How rubyists invert control?

Well, like this:

# File sandwich from Ruby Koans

def file_sandwich(file_name)  
  file = open(file_name)
  yield(file)
ensure  
  file.close if file
end

file_sandwich('file.txt') do |file|  
  puts file.count
end  

…and like that:

// Composition of React components

function Layout(props) {  
  return (
    <Grid> <Row> <Col xs={12}>
      {props.children}
    </Col> </Row> </Grid>
  );
}

function App() {  
  return (
    <Layout>
      <Content />
    </Layout>
  );
}

(I know, it's React not Ruby, but I expect at least some rubists to be familiar with it)

…and in a load of other ways, each sharing the same trait. 

The code of something to call (file_sandwich, Layout — let me dub it callee) is implemented in a certain way that leaves some gate open. The callee at some point gives the control back to the caller (inverts the control). 

In other words, the caller has to complement the implementation - or do nothing, which is sometimes sufficient.

And this is what they call Inversion of Control (IoC).


Foregoing examples share the “sandwich” pattern of yielding (rendering) the caller’s code in the middle — after some initialization and before cleanup. IoC doesn’t necessarily have to work this way. 

As an example consider event-driven programming. The heart of it lies in decoupling the reaction on the event from the fact of triggering the event itself. Think jQuery events:

$( "#target" ).click(function() {
  alert( "Handler for .click() called." );
});

...or RabbitMQ pub-sub:

queue.subscribe(:block => true) do |delivery_info, properties, body|  
  puts " [x] #{body}"
end  

These are examples of defining what should happen in case an event occurs. Of course, the other part of the code has to allow this in advance.

Once again the caller complements the implementation - triggering the event. The callee only knows when to do it.

At the end of the day, it comes down to the same mechanism of inverting the control. 


Yet another way to implement IoC is Dependency Injection. Let's see it in practice.

Dependency Injection

Let's imagine we have a service object using a logger, like this:

class CreateOrder  
  def call(item, quantity)
    # creating order
    logger = Logger.new(STDOUT)
    logger.info("Order created")
  end
end  

This code can perfectly serve the purpose, but it’s not flexible. As the codebase grows, it can very early become insufficient. For a couple of reasons it could be better to make the logger a moving part:

  • By passing another object, you can change the behavior of the logging feature without changing the code of the service itself (we’ll elaborate on this later).
  • You can reuse a logger across the whole application (or across some parts of it).
  • You can replace the logger in your tests, not to pollute STDOUT.
  • You can see by the signatures what’s used by each method (that’s a biggie in legacy systems).

Let’s refactor the code to be able to inject the dependency of the logger. By that I mean - to be able to change the logger from outside the CreateOrder.

One approach could be extracting a method like this:

class CreateOrder  
  def call(item, quantity)
    # creating order
    logger.info("Order created")
  end

  def logger
    Logger.new(STDOUT)
  end
end  

You can change the definition of logger by inheriting from CreateOrder and overwriting logger method. That’s an option, but overusing this manner is somehow compromised — see Nothing is Something for more details.

Anyway, it’s worth noting how easy it was to implement dependency injection. Extracting a method - that's it. Ruby and JS programmers are lucky (?) enough to be able to leverage duck typing. In statically typed languages people usually have to explicitly define interfaces and redefine existing dependencies to introduce Dependency Injection. We can just extract a method. This could be nice and easy first step in the refactoring process - even if you can do better.

And we can do better.

Instead of creating the object in the service, let’s assume it’s defined somewhere else. We can do it like this:

# Whatever uses CreateOrder, has to provide a logger

class CreateOrder  
  def call(item, quantity, logger)
    # creating order
    logger.info("Order created")
  end
end  

or better, like that:

# Effectively the same as above,
# but consistent with a nice convention —
# - separate dependencies (in constructor) from data (in call)

class CreateOrder  
  def initialize(logger)
    @logger = logger
  end

  def call(item, quantity)
    # creating order
    @logger.info("Order created")
  end
end  

or following a slightly different approach of global dependencies:

# You can also use for example dry-container

class CreateOrder  
  def call(item, quantity)
    # creating order
    Rails.logger.info("Order created")
  end
end  

Which one is best?

I equally like the last two versions. The biggest difference I find between the two is the way to test it. With logger being passed in the constructor, you are pushed to use test doubles (mock, spy) in unit tests. Personally, I think at some point it becomes very annoying — building a lot of doubles before you can write a single test. 

You can get rid of that when you use logger as a global dependency. You can write a dummy FakeLogger class, and pass it as Rails.logger when RAILS_ENV=test. Or you can still mock it as before, but hopefully, have less work with mocking it every single time.

On the other hand, not exposing the logger in the signature of the method makes it a hidden dependency.


What’s the benefit?

Dependency Injection and other types of Inversion of Control — what are they all for?

Makes your code more generic

Instead of defining methods like select_even, select_odd, select_positive etc. one select is enough. Even in the future, when mathematicians discover a new type of numbers — say, cool numbers — you can easily write your own select_cool

It’s SOLID

How cool is O (open/closed)? At first glance, it sounded ridiculously impossible to me. I mean, closed for bad things, and open for good ones? C’mon.

IoC is how you do it. You can extend the code without changing existing implementation.

What a cool idea it is! 

It’s a pattern

And as such, is extremely useful in discussions. Like during code review:

- Why do you yield it?
- I applied Inversion of Control so that this service can be used in a different type of controller.

or:

- How about introducing Dependency Injection here?
- I was thinking about it, but I decided it’s too early, we don’t know if it’s going to be reused anytime soon.

… and it’s unnecessary

At least — not every single time. The reason is — it can be expensive in introducing it and maintaining, especially in legacy, messy systems.

When introducing IoC you have to consider:

  • how big (little) should be the chuck of implementation to give back,
  • what layer (or how many of them) should have the possibility to affect the implementation. 

Also, it could be costly to maintain:

  • IoC breaks the linear flow of the code, which is simple. And you should keep in simple.
  • It can lead to over-engineering. You should probably skip injecting the logger if you write the Hello World program. And in simple, unreusable methods. 

Personally, I think of IoC in the same category as other design patterns. They are the solutions to a particular problem. No problem — no need for them.

So in easy, straightforward method/class/project — I tend to insist on not using IoC techniques. When the codebase grows, requirements change, etc. — it can make sense to introduce it. 

PS. Nobody knows better than you when it starts making sense in your project.

Speaking about… 

I used to confuse D - Dependency Inversion with IoC and DI, so it might be useful to put it in the context of the two. 

Dependency Injection, as explained above, is one of many ways to introduce Inversion of Control. 

Dependency Inversion principle says: implementation details should depend upon the abstraction, not the other way around. This way you should be able to change “details” without changing the “core”.

Hint: whenever this sounds difficult, leverage duck typing (interfaces).

For example in Rails, by convention, a controller (being responsible for business logic, so the “core”) renders the corresponding view (presentational details). Violation? No, because Rails uses file system’s ability to look up on a given path. If you need to change (replace) a way of presenting the controller’s data, all you have to do is deal with a view file. Controllers stay untouched. Safe and easy. Thank you, file system.

The bottom line is — Dependency Inversion could lead to Inversion of Control, but it’s not promised.