Is Independent Deployability / Developibility a Legacy Problem?

117 views
Skip to first unread message

Marko A.

unread,
Nov 12, 2016, 11:18:54 AM11/12/16
to Clean Code Discussion
As I understand the problem, you want to separate 2 parts of software into components so they are independently deployable. T
his is important so that they are independently  developable.
Why not just put them into separate gems / jar files? Why is that not enough?

For some reason there is an episode on Abstract Factory and Factory Method.
That doesn't sound like  good solution to this problem to me.

I have never coded in Java, only C# and ruby (only ruby now) and I hope Java has something similar to Bundler.
In ruby jar files are called gems.
Bundler is software that takes care of your dependencies, which gems you depend on and exact versions.

So if Component 1 is using Component 2 it would be declared like this in Gemfile (file that bundler is using to install gems)

gem 'component_2', '~1.0.0'  # means >= 1.0.0 and < 1.1

Running `bundle install` will install all dependencies and correct versions locally in the project.
Now group developing component 1 can use that version of component until they like. Component 2 can be independently developed.

Component 1 doesn't care about changes in component 2 because it is using a fixed version which never changes.
If they want to change, all they have to do is change version number in their Gemfile and run `bundle install` again.

To make this even easier, developers developing component 2 are using semver so everyone else can know what versions are compatible and what not.

When group developing component 2 wants to create a new version all they gotta do is create a tag in git saying something like `v1.0.1` and gem and bundler will know what version it is and what to do with it.
Why wouldn't this work in java?
Why would anyone use Abstract Factory or Factory method?
Is this a legacy problem or still exists in Java and why?

Łukasz Duda

unread,
Nov 14, 2016, 2:30:01 PM11/14/16
to Clean Code Discussion
You asked good questions.
Java has Maven https://maven.apache.org/. I think it's not about technology.
The factory lets you separate business rules from creation logic. Factory is situated in "MAIN" and it's responsible for construction. You can provide new implementation without changing existing one - better OCP compliance http://www.oodesign.com/open-close-principle.html.

I think you assume first component doesn't need second component's changes. Sometimes you have to develop 2 dependent components at the same time. Let me give an example to present the way I think about the factory.
Configuration 1 - tight coupling, no factory:
Use case from first component depends on gateway (repository) implementation from second component. It creates and uses gateway.
If gateway from component 2 changes the way connection is established, for example source of connection settings, you have to change and retest business rules from component 1, after providing connection settings in the new way.
Configuration 2 - loose coupling, factory:
Component 1 contains use case and gateway interface. Component 2 contains gateway implementation that depends on it's interface in component 1. Component 3 contains factory and depends on component 1 and component 2. The factory can create use case, create gateway and combine the two to work together.
If gateway from component 2 changes the way connection is established, you have to change component 2 and component 3, but you don't have to change or test component 2 with business rules.

Greetings

Marko Avlijas

unread,
Nov 15, 2016, 6:35:08 AM11/15/16
to Clean Code Discussion
I am not convinced by your example. Of course that when database changes something will have to change. When you change database in rails, you have to change your code that sets up the database.
This is how it's done in rails. You change the config/database.yml file (lets say that's database.json for non ruby developers)

development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: db_name_here
  pool: 5
  username: root
  password:
  host: localhost

There is no way to avoid this change. If you didn't put connection settings in txt file, if it's in the code they you have to change the code. But unless you are dupilicating that code all over, you have to change one function.  
I don't see how that prevents independent deployability / developibility. It would only if your code is so soaked-wet that you do not have establish_connection function anywhere and instead is duplicating that code all over your code base.


I have recently refactored my code to include Factory and I don't think that decision has any value on independent deployability.

Lets look at the code:

Before:

# using the converter
@converter = Converter.new(:weight)
new_value = @converter.to_metric(value)

class Converter
  def initialize(type)
    @type = type
  end

  def to_imperial(value)
    case type
    when :weight
      weight_to_imperial(value)
    when :speed_change, :range, :top_speed
      distance_to_imperial(value)
    end      
  end

  def to_metric(value)
    case type
    when :weight
      weight_to_metric(value)
    when :speed_change, :range, :top_speed
      distance_to_metric(value)
    end      
  end

  def weight_to_imperial(value)
    value * 2.204623
  end

  def weight_to_metric(value)
    value / 2.204623
  end

  def distance_to_imperial(value)
    value / 1.609344
  end

  def distance_to_metric(value)
    value * 1.609344
  end
end


After:

# using the converter
@converter = ConverterFactory.build_converter(:weight)
new_value = @converter.to_metric(value)


class ConverterFactory
  def self.build_converter(type)
    case type
    when :weight
      WeightConverter.new
    when :speed_change, :range, :top_speed
      DistanceConverter.new
    end
  end

  class WeightConverter
    def to_imperial(value)
      value * 2.204623
    end

    def to_metric(value)
      value / 2.204623
    end
  end

  class DistanceConverter
    def to_imperial(value)
      value / 1.609344
    end

    def to_metric(value)
      value * 1.609344
    end
  end
end



So anyone using this class would have to change their code. But I don't see how that would stop independent deployability / developability.
First of all, this change removes code duplication, but as someone developing other component, you do not care. You do not have to change immediately or at all. You can stick with old version of component.

If you decide that you do want to change, every time interfaces or class name changes, you will have to change your code. But why would that stop independent deployability / developability?



Łukasz Duda

unread,
Nov 15, 2016, 7:26:01 AM11/15/16
to Clean Code Discussion
You can't avoid the change, but you can isolate use case in component 1, so that you business rules can avoid the change.
In your example I don't see who uses converter. The main issue in my opinion is that the user doesn't have to create instance. The user can be developed in isolation from converter or gateway. It doesn't have to know about database connection.
Let me try to extend the example...

Example 1 - factory provides use case instance and its dependencies:

class UseCase1
  def initialize(converter)
    @converter = converter
  end

  def execute(value)
    new_value = converter.to_metric(value)
  end
end

In this example use case doesn't depend on concrete converter and doesn't have to be tested after converter's implementation changes. Business rule developers dealing with use cases can work independently from the team dealing with tools such as converter or gateway.

Example 2 - without factory

class UseCase1
  def execute(value)
    converter = Converter.new(:weight)
    new_value = converter.to_metric(value)
  end
end

In this example use case depends on converter's implementation and has to be tested after converter implementation is changed.

Łukasz Duda

unread,
Nov 15, 2016, 7:30:46 AM11/15/16
to Clean Code Discussion
But the independent development is possible as the result of inverting dependencies, not because of factory.
Maybe the example is too simple to reason extracting creation logic and implementation selection to the factory...

Marko Avlijas

unread,
Nov 15, 2016, 7:37:35 AM11/15/16
to Clean Code Discussion
But code has to be tested after every change. Obviously class UseCase1 in both examples has it's tests which need to pass after any line of code anywhere has changed.

I don't see how change from example 1 to example 2 reduces the need for tests?

I think this course is making a big deal over recompiling and independend deployment / developibility. 

Every time you add a line of code, you have to recompile - so what's the big deal?
Every time you add a line of code, you have to rerun your tests - so what's the big deal?
Every time code you depend on changes, you have to rerun tests - so what's the big deal?

I guess a clear real world example is needed. I just can't imagine dry code not being able to indepedently develop.

Marko Avlijas

unread,
Nov 15, 2016, 7:41:58 AM11/15/16
to Clean Code Discussion
What I meant by last sentence is this:

if you have a dry code - you have to make change in one place, not all over the place
then it doesn't matter much how the code is wired for independent developability.

I can't imagine how creative you'd have to be a wiring your code so badly so that it's hard to develop intepedently.
You might have to recompile. You might have to retest. But that's what you do all the time anyway.

Łukasz Duda

unread,
Nov 19, 2016, 6:34:48 AM11/19/16
to Clean Code Discussion
If you have DRY code without duplication, you probably already have some kind of factory method.
I found dependency inversion and isolated place for creating instance or selecting implementation most useful in solving two issues:
  • Dependency Inversion Principle allowed me to test drive my classes in isolation and focus on one small thing at the time, and protect against regression.
  • Factories in "MAIN" partition allowed me to "close" platform libraries (Open-Close Principle). Having platform dependency closed for modifications, I can still change it's behavior by changing factory to provide new implementation, when I need to. By platform libraries I mean part shared by many applications. I didn't want to duplicate this shared part in every application. It would be impossible to support changes to those libraries if they would sit in multiple places.
Reply all
Reply to author
Forward
0 new messages