Pages

Monday, February 9, 2015

The Dependency Elimination Principle: A Canonical Example

There's been quite a bit of discussion so far on the Dependency Elimination Principle. One thing obviously missing from my posts is a code example. This example illustrates a common class of unnecessary dependencies.

I'll use application configuration as the example. I'll uncreatively title the class of unnecessary dependencies: "The thing that gets the thing you need". And I'll uncreatively title the remedy to this problem: "Pass the thing you need, not the thing that gets the thing you need".

The config example


Most applications we write have a configuration defined via some XML or JSON file sitting near the executing code.  We want to keep the system modular and testable so we make sure the application code doesn't depend directly on this configuration file.  A common way we implement this looks similar to this:

Then when one of our application classes needs a value from the application's configuration, we give it a dependency on IConfigurationReader and let it read a config value in one of its methods. For this example, let's make a class that generates invoices. We have multiple environments and don't want to mistake test invoices for real ones. So we decide to put a watermark on invoices generated outside of production. One way to accomplish this would be to create a "invoiceWatermarkText" config setting that is set in test/staging and not set in production. Here's what this might look like:

This is SOLID, decoupled code. InvoiceGenerator rightly doesn't know about the details of how configuration works. And it's testable because you can stub out the configuration reader. However, there are still problems.

Loose coupling is still coupling


Although InvoiceGenerator doesn't know about how configuration works, it still has to know there is such a thing as configuration. This makes things more complicated. When you test, you have to provide a stub. And when you create an InvoiceGenerator in the production code, you have to provide a real configuration. InvoiceGenerator is still coupled to configuration, thus making it impossible to use it without supplying some sort of configuration reader.

Eliminate the coupling


We can improve this with a simple fix: Pass the thing we need (the watermark text), not the thing that gets the thing we need (the IConfigurationReader). It will look something like this:

Now we can painlessly use an InvoiceGenerator literally anywhere in any application. And there's less to think about when reasoning about it.

But...we still need the config part


You're probably wondering, "Ok, so I still need to read that text from config to give it to the InvoiceGenerator. Where does that code go?". You have a couple options here:
  • Just read it from config on the line that is calling GenerateInvoice
  • Create a one-line overload to GenerateInvoice that reads from the real configuration reader.  No need to test that line.

Wrapping it all up


We often introduce a dependency so we can get the thing we need.  However, we usually only need the thing we need.  So we pass the thing we need, not the thing that gets the thing we need.

11 comments:

  1. Nice article! This fits in very nicely with the idea that programs should primarily be just a series of data transformations.

    I'm curious if you would consider this use of a raw string as "Primitive Obsession". When would you promote it to a first-class type?

    ReplyDelete
    Replies
    1. Yup, I've been noticing that same pattern that programs really are just a set of data transformations.

      RE: Primitive Obsession, in this case I likely wouldn't introduce a new type. I would introduce the new type once I'm doing something interesting with the text instead of just passing it around. Some indicators that would lead me to introduce the type:

      - Extracting a private method that operates on the text e.g. ValidateWatermarkText or SantizeWatermarkText
      - Comparing it to other instances of text, especially a comparison that will change how we interpret case
      - Writing a test against InvoiceGenerator that is making sure something special happens with the watermark text.

      Delete
    2. I typed a nice reply and then lost it when I signed in ...

      How about a companion article to this one that deals with the topic of Primitive Obsession. I think a going over some of your points with a good example of how to recognize and handle it would be a nice progression in helping developers find a nice balance.

      Delete
    3. Yup, I have a few articles in mind related to primitive obsession. Another common case for injecting dependencies is what I'm currently calling "Primitive Support". Examples: IEmailValidator, IEncrypter, etc. These are interfaces introduced to enforce some valid state for a primitive. The way to eliminate these kinds of dependencies is to introduce whole values into the type system (EmailAddress, Password, etc) and use those instead of the primitive + support interfaces.

      Delete
    4. Nice. I'll be looking forward to reading/forwarding them. My current team has a LONG way to go in these areas.

      Delete
    5. Great article. :)

      +1 for Dan's request for Primitive Obsession article next.

      A lot of this stuff is new to me, but I'm starting to identify more of it in our code base.

      Delete
  2. I really like this. Right now I'm favouring creating the overload that we simply don't test (in an automated fashion of course) - this overload just becomes your config/DI container in essence.

    I've blogged about limiting the amount of dependencies you use - so this post really hits home. I should add this on as another valid technique.

    ReplyDelete
  3. Thank you for illustrating your thesis with a concrete example. I agree that the new code is better, and that is where I would usually end up as well… while feeling that I was being guided by the SOLID guidelines.

    Specifically, passing an IConfigurationReader to an InvoiceGenerator seems to me like a blatant violation of the Interface Segregation Principle (do not depend on methods you do not use).

    It might seem that IConfigurationReader could not be "segregated" any further, seeing as how it contains a single method. However, if one looks past the "interface" keyword of the implementation language, one can actually see IConfigurationReader.Get() as a gateway to an infinitely wide interface — a client depending on IConfigurationReader is effectively not communicating which configuration values (methods) it may depend on, and that to me is the big smell.

    Likewise, I would argue that your (String watermarkText) is an interface, in the sense that it precisely describes what GenerateInvoice depends on. And therefore it would be the result of correctly reducing GenerateInvoice's dependencies to their bare minimum.

    What do you think ?

    ReplyDelete
    Replies
    1. Haven't thought about ISP in that way before -- specifically where ISP applies not only the methods on the dependency, but also to the surface area that the given dependency exposes. IConfigurationReader at first looks like it's segregated, but it's actually not because it gives you access to all possible config values.

      Thanks for your thoughts.

      Delete
  4. After Eliminate the coupling the InvoiceGenerator makes use of no constructor and no state. Should the class be made static?

    ReplyDelete
  5. Why there are always so simple examples used?
    What when we need to make more calls and not always to execute them?
    if (is tuesday) then Config.GetTuesdayPromotion()
    if (is 12pm) otherDep.GetFromAPI(config.GetUrl)
    etc.

    ReplyDelete