Programming ≈ Fun

Written by Krešimir Bojčić

Dependency Injection In Ruby

Lately I’ve been using a lot of Dependency Injection in a big enterprise application as a way to satisfy different customers needs. Application is being used in couple of different countries and it is tailor made to match specific needs and specific regulation laws. It’s being developed by three dislocated teams and since it’s written in .NET 100% I think it was the right way to go. That got me wondering if the similar setup would be needed in Ruby world.

I’ve noticed a couple of blog posts claiming that you don’t need Dependency Injection since Ruby is so malleable. I don’t fully agree.

I do agree with Jamis Buck that it is generally a bad idea to speculate about where you will need Dependency Injection and to put infrastructure up front for it.

If you are unsure what a Dependency Injection is take a look at the example below:

class Customer
  def initialize(id)
    @id = id
  end

  def tax_code
    "US-#{@id}"
  end
end

print Customer.new(123456).tax_code

If you follow the Single responsibility principle you’ll extract the code for tax code calculation in different class which is responsible for it:

class TaxCode
  def self.generate(id)
    "US-#{id}"
  end
end

#change in Customer
class Customer
  ...
  def tax_code
    TaxCode.generate(@id)
  end
end

print Customer.new(123456).tax_code

So far, so good. I urge you never to speculate whether you’ll need extendibility. Why? It’s really easy to add later on if the need arises. As Avdi Grimm says in his Objects on Rails

Much like premature optimization, premature change management usually misses the mark

Now let imagine our application is a smashing success and we are selling in Brazil also. They have a bit different algorithm for tax_code:

class BrazilianTaxCode
  def self.generate(id)
    "#{id + 9}-BRA"
  end
end

We can panic and add tax_code_brazilian to the Customer:

class Customer
  ...
  def tax_code_brazilian
    TaxCodeBrazilian.generate(@id)
  end
end

We could add type param and that is probably what I would do if I only had two customers:

class Customer
  ...
  def tax_code(type=:us)
    return TaxCode.generate(@id) if type == :us
    TaxCodeBrazilian.generate(@id)
  end
end

Dependency Injection pattern helps when methods like the one above begin to get out of hands. This is how you can do it:

(setter_injection.rb) download
class Customer
  attr_writer :tax_code

  def tax_code(id)
    tax_code.generate(id)
  end

end

c = Customer.new(123456)
c.tax_code = BrazilianTaxCode
print c.tax_code

Basically you just postpone the decision of what tax_code generator is going to be used. It is also beneficial as a way to decouple Customer from TaxCode generators, since Customer doesn’t event know the type of the generator it will use. In real world this also eases up testing in isolation.

Variation on a theme would be setting generator on initialize:

(constructor_injection.rb) download
class Customer
  def initialize(tax_code = TaxCode)
    @tax_code = tax_code
  end

  def tax_code
    @tax_code.generate(@id)
  end
end

You may want some kind of configuration so you don’t need to create factory and/or think about it. I think this would be the easiest way to accomplish that:

@@settings = { tax_code:BrazilianTaxCode}

class Customer
  def tax_code
    @@settings[:tax_code].generate(@id)
  end
end

Let’s develop it some more to make it somewhat more appealing:

class Configuration
  @settings = {}
  def self.register(attributes)
    attributes.each { |k,v| @settings[k] = v }
  end

  def self.[](key)
    @settings[key]
  end
end

#registration
Configuration.register(tax_code_generator:BrazilianTaxCode)

class Customer
  def tax_code
    Configuration[:tax_code_generator].generate(@id)
  end
end

Why stop there, we can easily use Ruby glorious block syntax:

class Configuration
  @settings = {}
  def self.register(&block)
    block.call(self)
  end

  def self.tax_code_generator=(value)
    @settings[:tax_code_generator] = value
  end

  def self.method_missing(m)
    @settings[m]
  end
end

#registration
Configuration.register do |config|
  config.tax_code_generator = BrazilianTaxCode
end

Now I know we are onto something because this is exactly how it’s done all over the various Ruby gems.

Couple of iterations later I’ve ended up with this syntax. I’ve also renamed it to Implementation since it is bound to different implementations, so it kinda makes more sense:

Implementation.setup do |i|
  i.tax_code = :brazilian_tax_code
end

class Customer
  ...
  def tax_code
    Implementation.tax_code.generate(@id)
  end
end

print Customer.new(123456).tax_code

This gives me a central place to configure system. It gives me peace of mind not to speculate up front what needs to be extendible and I would argue it makes code more pleasable in case of lots of different implementation variants.

For a conclusion I can say:

We don’t need heavy Dependency Injection framework in Ruby, but we sure need the idea of “inversion of control”. The fact that it is so easy to do is a reason that we don’t need framework (and also the reason why I love Ruby so much).

If you are interested here are the whole 13 lines of code. I apologize for the lack of my Ruby skills and am open for suggestions :)

(service_locator.rb) download
require 'active_support/core_ext/string'
class Implementation
  @settings = {}
  def self.setup(&block)
    block.call(self)
  end
  def self.method_missing(m, *args, &block)
    m = m.to_s.gsub('=','')
    @settings[m] = args[0] if args.size == 1
    #notice the new, I can now loose self in generators
    @settings[m].to_s.camelize.constantize.new
  end
end

Of course in Ruby there is alternative to this. You can put different implementations for different customers in customer folder and monkey patch the behaviour that needs to be different. I don’t find that approach better than the one listed above.

In my opinion it’s better to be explicit about what is pluggable. Also monkey patching and playing with load paths forces you to develop in customer folder and I think you are better of developing in feature folder. The bigger the project and the bigger the team is the harder it is to know exactly what algorithms you have supported so far. Having them all in one folder named after the feature really helps a lot.

EDIT: After checking my notes with the authority (and my personal hero) on the subject Martin Fowler I would say that I’ve described two different patterns that both in effect decouple from concrete class implementation.

We had two variants of Dependency Injection, first being Setter Injection and the second Constructor Injection (Third is Interface Injection but that doesn’t make much sense in Ruby). Next pattern was Service Locator that was coded as Implementation class. I’ve marked the code snippets accordingly.

I think part of bad reputation that Dependency Injection has in community is because people imagine a lot of overhead and red tape that is needed in let say Java or .NET.

Ruby makes is all so much more natural. We may end up not noticing that we achieved decoupling that was the goal of both Dependency Injection and Service Locator.

« Speed comparison (Haskell, Ruby, Python, CLISP) Unix Command Line Fu »

Comments

Copyright © 2019 - Kresimir Bojcic (LinkedIn Profile)