How to create a Dependency Injection-friendly software library.

In my book, I go to great lengths to explain how to develop loosely coupled applications using various Dependency Injection (DI) patterns, including the Composition Root pattern. With the great emphasis on applications, I didn't particularly go into details about making DI-friendly libraries. Partly this was because I didn't think it was necessary, but since one of my highest voted Stack Overflow answers deal with this question, it may be worth expanding on.

In this article, I will cover libraries, and in a later article I will deal with frameworks. The distinction I usually make is:

  • A Library is a reusable set of types or functions you can use from a wide variety of applications. The application code initiates communication with the library and invokes it.
  • A Framework consists of one or more libraries, but the difference is that Inversion of Control applies. The application registers with the framework (often by implementing one or more interfaces), and the framework calls into the application, which may call back into the framework. A framework often exists to address a particular general-purpose Domain (such as web applications, mobile apps, workflows, etc.).

Most well-designed libraries are already DI-friendly - particularly if they follow the SOLID principles, because the Dependency Inversion Principle (the D in SOLID) is the guiding principle behind DI.

Still, it may be valuable to distil a few recommendations.

Program to an interface, not an implementation

If your library consists of several collaborating classes, define proper interfaces between these collaborators. This enables clients to redefine part of your library's behaviour, or to slide cross-cutting concerns in between two collaborators, using a Decorator.

Be sure to define these interfaces as Role Interfaces.

An example of a small library that follows this principle is Hyprlinkr, which defines two interfaces used by the main RouteLinker class:

public interface IRouteValuesQuery
{
    IDictionary<stringobject> GetRouteValues(
        MethodCallExpression methodCallExpression);
}

and

public interface IRouteDispatcher
{
    Rouple Dispatch(
        MethodCallExpression method,
        IDictionary<stringobject> routeValues);
}

This not only makes it easier to develop and maintain the library itself, but also makes it more flexible for users.

Use Constructor Injection

Favour the Constructor Injection pattern over other injection patterns, because of its simplicity and degree of encapsulation.

As an example, Hyprlinkr's main class, RouteLinker, has this primary constructor:

private readonly HttpRequestMessage request;
private readonly IRouteValuesQuery valuesQuery;
private readonly IRouteDispatcher dispatcher;
 
public RouteLinker(
    HttpRequestMessage request,
    IRouteValuesQuery routeValuesQuery,
    IRouteDispatcher dispatcher)
{
    if (request == null)
        throw new ArgumentNullException("request");
    if (routeValuesQuery == null)
        throw new ArgumentNullException("routeValuesQuery");
    if (dispatcher == null)
        throw new ArgumentNullException("dispatcher");
 
    this.request = request;
    this.valuesQuery = routeValuesQuery;
    this.dispatcher = dispatcher;
}

Notice that it follows Nikola Malovic's 4th law of IoC that Injection Constructors should be simple.

Although not strictly required in order to make a library DI-friendly, expose every injected dependency as an Inspection Property - it will make the library easier to use when composed in one place, but used in another place. Again, Hyprlinkr does that:

public IRouteValuesQuery RouteValuesQuery
{
    get { return this.valuesQuery; }
}

and so on for its other dependencies, too.

Consider an Abstract Factory for short-lived objects

Sometimes, your library must create short-lived objects in order to do its work. Other times, the library can only create a required object at run-time, because only at run-time is all required information available. You can use an Abstract Factory for that.

The Abstract Factory doesn't always have to be named XyzFactory; in fact, Hyprlinkr's IRouteDispatcher interface is an Abstract Factory, although it's in disguise because it has a different name.

public interface IRouteDispatcher
{
    Rouple Dispatch(
        MethodCallExpression method,
        IDictionary<stringobject> routeValues);
}

Notice that the return value of an Abstract Factory doesn't have to be another interface instance; in this case, it's an instance of the concrete class Rouple, which is a data structure without behaviour.

Consider a Facade

If some objects are difficult to construct, because their classes have complex constructors, consider supplying a Facade with a good default combination of appropriate dependencies. Often, a simple alternative to a Facade is Constructor Chaining:

public RouteLinker(HttpRequestMessage request)
    : this(request, new DefaultRouteDispatcher())
{
}
 
public RouteLinker(HttpRequestMessage request, IRouteValuesQuery routeValuesQuery)
    : this(request, routeValuesQuery, new DefaultRouteDispatcher())
{
}
 
public RouteLinker(HttpRequestMessage request, IRouteDispatcher dispatcher)
    : this(request, new ScalarRouteValuesQuery(), dispatcher)
{
}
 
public RouteLinker(
    HttpRequestMessage request,
    IRouteValuesQuery routeValuesQuery,
    IRouteDispatcher dispatcher)
{
    if (request == null)
        throw new ArgumentNullException("request");
    if (routeValuesQuery == null)
        throw new ArgumentNullException("routeValuesQuery");
    if (dispatcher == null)
        throw new ArgumentNullException("dispatcher");
 
    this.request = request;
    this.valuesQuery = routeValuesQuery;
    this.dispatcher = dispatcher;
}

Notice how the Routelinker class provides appropriate default values for those dependencies it can.

However, a Library with a more complicated API could potentially benefit from a proper Facade. One way to make the API's extensibility points discoverable is by implementing the Facade as a Fluent Builder. The following RouteLinkerBuilder isn't part of Hyprlinkr, because I consider the Constructor Chaining alternative simpler, but it could look like this:

public class RouteLinkerBuilder
{
    private readonly IRouteValuesQuery valuesQuery;
    private readonly IRouteDispatcher dispatcher;
 
    public RouteLinkerBuilder()
        : this(new ScalarRouteValuesQuery(), new DefaultRouteDispatcher())
    {
    }
 
    private RouteLinkerBuilder(
        IRouteValuesQuery valuesQuery,
        IRouteDispatcher dispatcher)
    {
        this.valuesQuery = valuesQuery;
        this.dispatcher = dispatcher;
    }
 
    public RouteLinkerBuilder WithValuesQuery(IRouteValuesQuery newValuesQuery)
    {
        return new RouteLinkerBuilder(newValuesQuery, this.dispatcher);
    }
 
    public RouteLinkerBuilder WithDispatcher(IRouteDispatcher newDispatcher)
    {
        return new RouteLinkerBuilder(this.valuesQuery, newDispatcher);
    }
 
    public RouteLinker Create(HttpRequestMessage request)
    {
        return new RouteLinker(request, this.valuesQuery, this.dispatcher);
    }
 
    public IRouteValuesQuery ValuesQuery
    {
        get { return this.valuesQuery; }
    }
 
    public IRouteDispatcher Dispatcher
    {
        get { return this.dispatcher; }
    }
}

This has the advantage that it's easy to get started with the library:

var linker = new RouteLinkerBuilder().Create(request);

This API is also discoverable, because Intellisense helps users discover how to deviate from the default values:

Intellisense and Fluent Builder combined enhances discoverability

It enables users to override only those values they care about:

var linker =
    new RouteLinkerBuilder().WithDispatcher(customDispatcher).Create(request);

If I had wanted to force users of Hyprlinkr to use the (hypothetical) RouteLinkerBuilder, I could make the RouteLinker constructor internal, but I don't much care for that option; I prefer to empower my users, not constrain them.

Composition

Any application that uses your library can compose objects from it in its Composition Root. Here's a hand-coded example from one of Grean's code bases:

private static RouteLinker CreateDefaultRouteLinker(HttpRequestMessage request)
{
    return new RouteLinker(
        request,
        new ModelFilterRouteDispatcher(
            new DefaultRouteDispatcher()
        )
    );
}

This example is just a small helper method in the Composition Root, but as you can see, it composes a RouteLinker instance using our custom ModelFilterRouteDispatcher class as a Decorator for Hyprlinkr's built-in DefaultRouteDispatcher.

However, it would also be easy to configure a DI Container to do this instead.

Summary

If you follow SOLID, and normal rules for encapsulation, your library is likely to be DI-friendly. No special infrastructure is required to add 'DI support' to a library.


Comments

Maris Krivtezs

I found great library for in process messaging made by Jimmy Bogard - MediatR, but it uses service locator. Implemented mediator uses service locator to lookup for handlers matching message type registered in container. Source.

What would be best approach to eliminate service locator in this case? Would it be better to pass all handler instances in mediator constructor and then lookup for matching one?

2014-06-02 20:10 UTC

Maris, thank you for writing. Hopefully, this article answers your question.

2014-06-03 9:20 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Monday, 19 May 2014 08:20:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!