C# – Balancing dependency injection with public API design

apic

I've been contemplating how to balance testable design using dependency injection with providing simple fixed public API. My dilemma is: people would want to do something like var server = new Server(){ ... } and not have to worry about creating the many dependencies and graph of dependencies that a Server(,,,,,,) may have. While developing, I don't worry too much, as I use an IoC/DI framework to handle all that (I'm not using the lifecycle management aspects of any container, which would complicate things further).

Now, the dependencies are unlikely to be re-implemented. Componentisation in this case is almost purely for testability (and decent design!) rather than creating seams for extension, etc. People will 99.999% of the time wish to use a default configuration. So. I could hardcode the dependencies. Don't want to do that, we lose our testing! I could provide a default constructor with hard-coded dependencies and one which takes dependencies. That's… messy, and likely to be confusing, but viable. I could make the dependency receiving constructor internal and make my unit tests a friend assembly (assuming C#), which tidies the public API but leaves a nasty hidden trap lurking for maintenance. Having two constructors which are implicitly connected rather than explicitly would be bad design in general in my book.

At the moment that's about the least evil I can think of. Opinions? Wisdom?

Best Answer

Dependency injection is a powerful pattern when used well, but all too often its practitioners become dependent on some external framework. The resulting API is quite painful for those of us who don't want to loosely tie our app together with XML duct tape. Don't forget about Plain Old Objects (POO).

First consider how you would expect someone to use your API--without the framework involved.

  • How do you instantiate the Server?
  • How do you extend the Server?
  • What do you want to expose in the external API?
  • What do you want to hide in the external API?

I like the idea of providing default implementations for your components, and the fact that you have those components. The following approach is a perfectly valid, and decent OO practice:

public Server() 
    : this(new HttpListener(80), new HttpResponder())
{}

public Server(Listener listener, Responder responder)
{
    // ...
}

As long as you document what the default implementations for the components are in the API docs, and provide one constructor that performs all the setup, you should be fine. The cascading constructors are not fragile as long as the set up code happens in one master constructor.

Essentially, all the publicly facing constructors would have the different combinations of components you want to expose. Internally, they would populate the defaults and defer to a private constructor that completes the setup. In essence, this provides you some DRY and is pretty easy to test.

If you need to provide friend access to your tests to set up the Server object to isolate it for testing, go for it. It won't be part of the public API, which is what you want to be careful with.

Just be kind to your API consumers and don't require a IoC/DI framework to use your library. To get a feel for the pain you are inflicting make sure your unit tests don't rely on the IoC/DI framework either. (It's a personal pet peeve, but as soon as you introduce the framework it's no longer unit testing--it becomes integration testing).

Related Topic