Dependency Inject (DI) "friendly" library
Asked Answered
C

4

236

I'm pondering the design of a C# library, that will have several different high level functions. Of course, those high-level functions will be implemented using the SOLID class design principles as much as possible. As such, there will probably be classes intended for consumers to use directly on a regular basis, and "support classes" that are dependencies of those more common "end user" classes.

The question is, what is the best way to design the library so it is:

  • DI Agnostic - Although adding basic "support" for one or two of the common DI libraries (StructureMap, Ninject, etc) seems reasonable, I want consumers to be able to use the library with any DI framework.
  • Non-DI usable - If a consumer of the library is using no DI, the library should still be as easy to use as possible, reducing the amount of work a user has to do to create all these "unimportant" dependencies just to get to the "real" classes they want to use.

My current thinking is to provide a few "DI registration modules" for the common DI libraries (e.g a StructureMap registry, a Ninject module), and a set or Factory classes that are non-DI and contain the coupling to those few factories.

Thoughts?

Coroner answered 12/1, 2010 at 0:20 Comment(1)
The new reference to broken link article (SOLID): butunclebob.com/ArticleS.UncleBob.PrinciplesOfOodBrynn
S
372

This is actually simple to do once you understand that DI is about patterns and principles, not technology.

To design the API in a DI Container-agnostic way, follow these general principles:

Program to an interface, not an implementation

This principle is actually a quote (from memory though) from Design Patterns, but it should always be your real goal. DI is just a means to achieve that end.

Apply the Hollywood Principle

The Hollywood Principle in DI terms says: Don't call the DI Container, it'll call you.

Never directly ask for a dependency by calling a container from within your code. Ask for it implicitly by using Constructor Injection.

Use Constructor Injection

When you need a dependency, ask for it statically through the constructor:

public class Service : IService
{
    private readonly ISomeDependency dep;

    public Service(ISomeDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public ISomeDependency Dependency
    {
        get { return this.dep; }
    }
}

Notice how the Service class guarantees its invariants. Once an instance is created, the dependency is guaranteed to be available because of the combination of the Guard Clause and the readonly keyword.

Use Abstract Factory if you need a short-lived object

Dependencies injected with Constructor Injection tend to be long-lived, but sometimes you need a short-lived object, or to construct the dependency based on a value known only at run-time.

See this for more information.

Compose only at the Last Responsible Moment

Keep objects decoupled until the very end. Normally, you can wait and wire everything up in the application's entry point. This is called the Composition Root.

More details here:

Simplify using a Facade

If you feel that the resulting API becomes too complex for novice users, you can always provide a few Facade classes that encapsulate common dependency combinations.

To provide a flexible Facade with a high degree of discoverability, you could consider providing Fluent Builders. Something like this:

public class MyFacade
{
    private IMyDependency dep;

    public MyFacade()
    {
        this.dep = new DefaultDependency();
    }

    public MyFacade WithDependency(IMyDependency dependency)
    {
        this.dep = dependency;
        return this;
    }

    public Foo CreateFoo()
    {
        return new Foo(this.dep);
    }
}

This would allow a user to create a default Foo by writing

var foo = new MyFacade().CreateFoo();

It would, however, be very discoverable that it's possible to supply a custom dependency, and you could write

var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();

If you imagine that the MyFacade class encapsulates a lot of different dependencies, I hope it's clear how it would provide proper defaults while still making extensibility discoverable.


FWIW, long after writing this answer, I expanded upon the concepts herein and wrote a longer blog post about DI-Friendly Libraries, and a companion post about DI-Friendly Frameworks.

Slivovitz answered 12/1, 2010 at 8:42 Comment(10)
While this sounds great on paper, in my experience once you have lots of inner components interacting in complex ways, you end up with lots of factories to manage, making maintenance harder. Plus, factories will have to manage their created components' lifestyles, once you install the library in a real container this will conflict with the container's own lifestyle management. Factories and facades get in the way of the real containers.Conspiracy
I have yet to find a project that does all of this.Conspiracy
Well, that's how we develop software at Safewhere, so we don't share your experience...Slivovitz
I know about the general principles involved when designing something to use DI/IoC. In particular, I was asking about ways to design/simplify a library's use by other consumers, who may choose to use a container I favor, such as Ninject or StructureMap, or some alternative. What specific ways can I ease use of the "high-level" API while maintaining proper DI. The Facade approach is something I had concidered, but the question becomes: Do the facade class(es) depend directly on the implementations? If not, how do they get their dependencies injected?Coroner
I updated my answer by elaborating the section on Facades - I hope that makes it a bit clearer.Slivovitz
Is the MyFacade suitable for hiding an ioc container implementation ie. let the CreateFoo method resolve Foo from the container?Civilize
I think that the facades should be hand-coded because they represent known (and presumably common) combinations of components. A DI Container should not be necessary because everything can be wired up by hand (think Poor Man's DI). Recall that a Facade is just an optional convenience class for users of your API. Advanced users may still want to bypass the Facade and wire up the components to their own liking. They may want to use their own DI Contaier for this, so I think it would be unkind to force a particular DI Container upon them if they are not going to use it. Possible but not advisableSlivovitz
also check out Common Service LocatorLamrouex
@MarkSeemann, should you rephrase the principle "program to interface" to be consistent with your post Interfaces are not abstractionsBeilul
This may be the single best answer I've ever seen on SO.Scorify
U
43

The term "dependency injection" doesn't specifically have anything to do with an IoC container at all, even though you tend to see them mentioned together. It simply means that instead of writing your code like this:

public class Service
{
    public Service()
    {
    }

    public void DoSomething()
    {
        SqlConnection connection = new SqlConnection("some connection string");
        WindowsIdentity identity = WindowsIdentity.GetCurrent();
        // Do something with connection and identity variables
    }
}

You write it like this:

public class Service
{
    public Service(IDbConnection connection, IIdentity identity)
    {
        this.Connection = connection;
        this.Identity = identity;
    }

    public void DoSomething()
    {
        // Do something with Connection and Identity properties
    }

    protected IDbConnection Connection { get; private set; }
    protected IIdentity Identity { get; private set; }
}

That is, you do two things when you write your code:

  1. Rely on interfaces instead of classes whenever you think that the implementation might need to be changed;

  2. Instead of creating instances of these interfaces inside a class, pass them as constructor arguments (alternatively, they could be assigned to public properties; the former is constructor injection, the latter is property injection).

None of this presupposes the existence of any DI library, and it doesn't really make the code any more difficult to write without one.

If you're looking for an example of this, look no further than the .NET Framework itself:

  • List<T> implements IList<T>. If you design your class to use IList<T> (or IEnumerable<T>), you can take advantage of concepts like lazy-loading, as Linq to SQL, Linq to Entities, and NHibernate all do behind the scenes, usually through property injection. Some framework classes actually accept an IList<T> as a constructor argument, such as BindingList<T>, which is used for several data binding features.

  • Linq to SQL and EF are built entirely around the IDbConnection and related interfaces, which can be passed in via the public constructors. You don't need to use them, though; the default constructors work just fine with a connection string sitting in a configuration file somewhere.

  • If you ever work on WinForms components you deal with "services", like INameCreationService or IExtenderProviderService. You don't even really know what what the concrete classes are. .NET actually has its own IoC container, IContainer, which gets used for this, and the Component class has a GetService method which is the actual service locator. Of course, nothing prevents you from using any or all of these interfaces without the IContainer or that particular locator. The services themselves are only loosely-coupled with the container.

  • Contracts in WCF are built entirely around interfaces. The actual concrete service class is usually referenced by name in a configuration file, which is essentially DI. Many people don't realize this but it is entirely possible to swap out this configuration system with another IoC container. Perhaps more interestingly, the service behaviors are all instances of IServiceBehavior which can be added later. Again, you could easily wire this into an IoC container and have it pick the relevant behaviors, but the feature is completely usable without one.

And so on and so forth. You'll find DI all over the place in .NET, it's just that normally it's done so seamlessly that you don't even think of it as DI.

If you want to design your DI-enabled library for maximum usability then the best suggestion is probably to supply your own default IoC implementation using a lightweight container. IContainer is a great choice for this because it's a part of the .NET Framework itself.

Unbodied answered 12/1, 2010 at 2:30 Comment(6)
The real abstraction for a container is IServiceProvider, not IContainer.Conspiracy
@Mauricio: You're right of course, but try explaining to someone who's never used the LWC system why IContainer is not actually the container in one paragraph. ;)Unbodied
Aaron, just curious as to why you use private set; instead of just specifying the fields as readonly?Maggy
@Jreeter: You mean why aren't they private readonly fields? That's great if they're only used by the declaring class but the OP specified that this was framework/library-level code, which implies subclassing. In such cases you typically want important dependencies exposed to subclasses. I could have written a private readonly field and a property with explicit getters/setters, but... wasted space in an example, and more code to maintain in practice, for no real benefit.Unbodied
Thank you for clarifying! I assumed readonly fields would be accessible by the child.Maggy
@Maggy This is also related to the notion that the interface offered to child classes (protected+public members) should be treated with as much care and attention as the public interface. More information here and here. Hence, protected fields are seldom a good idea for the same reasons public fields aren't.Deflocculate
C
4

EDIT 2015: time has passed, I realize now that this whole thing was a huge mistake. IoC containers are terrible and DI is a very poor way to deal with side effects. Effectively, all of the answers here (and the question itself) are to be avoided. Simply be aware of side effects, separate them from pure code, and everything else either falls into place or is irrelevant and unnecessary complexity.

Original answer follows:


I had to face this same decision while developing SolrNet. I started with the goal of being DI-friendly and container-agnostic, but as I added more and more internal components, the internal factories quickly became unmanageable and the resulting library was inflexible.

I ended up writing my own very simple embedded IoC container while also providing a Windsor facility and a Ninject module. Integrating the library with other containers is just a matter of properly wiring the components, so I could easily integrate it with Autofac, Unity, StructureMap, whatever.

The downside of this is that I lost the ability to just new up the service. I also took a dependency on CommonServiceLocator which I could have avoided (I might refactor it out in the future) to make the embedded container easier to implement.

More details in this blog post.

MassTransit seems to rely on something similar. It has an IObjectBuilder interface which is really CommonServiceLocator's IServiceLocator with a couple more methods, then it implements this for each container, i.e. NinjectObjectBuilder and a regular module/facility, i.e. MassTransitModule. Then it relies on IObjectBuilder to instantiate what it needs. This is a valid approach of course, but personally I don't like it very much since it's actually passing around the container too much, using it as a service locator.

MonoRail implements its own container as well, which implements good old IServiceProvider. This container is used throughout this framework through an interface that exposes well-known services. To get the concrete container, it has a built-in service provider locator. The Windsor facility points this service provider locator to Windsor, making it the selected service provider.

Bottom line: there is no perfect solution. As with any design decision, this issue demands a balance between flexibility, maintainability and convenience.

Conspiracy answered 12/1, 2010 at 1:46 Comment(8)
While I respect your opinion, I find your over-generalized "all other answers here are outdated and should be avoided" extremely naive. If we learned anything since then, it's that dependency injection is an answer to language limitations. Without evolving or abandoning our current languages, it seems we will need DI to flatten our architectures and achieve modularity. IoC as a general concept is just a consequence of these goals and definitely a desirable one. IoC containers are absolutely not necessary for either IoC or DI, and their usefulness depends on the module compositions we make.Deflocculate
@Deflocculate Your mention of me being "extremely naïve" is a non-welcome ad-hominem. I've written complex applications without DI or IoC in C#, VB.NET, Java (languages you'd think "need" IoC apparently judging by your description), it's definitely not about languages limitations. It's about conceptual limitations. I invite you to learn about functional programming instead of resorting to ad-hominem attacks and poor arguments.Conspiracy
I mentioned I found your statement to be naive; this says nothing about you personally and is all about my opinion. Please don't feel insulted by a random stranger. Implying I'm ignorant of all relevant functional programming approaches is not helpful either; you don't know that, and you'd be wrong. I knew you were knowledgeable there (quickly looked at your blog), which is why I found it annoying that a seemingly knowledgable guy would say things like "we don't need IoC". IoC happens literally everywhere in functional programming(..)Deflocculate
and in high-level software in general. In fact, functional languages (and many other styles) do in fact prove that they solve IoC without DI, I think we both agree with that, and that's all I wanted to say. If you managed without DI in the languages you mention, how did you do it? I assume not by using ServiceLocator. If you apply functional techniques, you end up with the equivalent of closures, which are dependency-injected classes (where dependencies are the closed-over variables). How else? (I'm genuinely curious, because I don't like DI either.)Deflocculate
IoC containers are global mutable dictionaries, taking away parametricity as a tool for reasoning, therefore to be avoided. IoC (the concept) as in "don't call us we'll call you" does apply technically every time you pass a function as a value. Instead of DI, model your domain with ADTs then write interpreters (c.f. Free).Conspiracy
@Deflocculate When passing a function for "DI", if you have to model that function with IO in Haskell then ask yourself whether there's a way to do that avoiding IO. IO is a denotative tarpit.Conspiracy
Fully agreed on all counts. I share your views on containers (along with pretty much everyone else now), and looking at higher-order functions as enabling a kind of IoC is what I was getting at. I also need to think about the 'homomorphism' between IoC and interpretation. Intuitively your perspective seems very sound though. Unfortunately, the lack of ADT support in current "mainstream" languages (as per the list you gave) make these approaches difficult to envision. Hence, I'm still puzzled as to how you could avoid DI in these languages without losing the benefits.Deflocculate
@Deflocculate You can easily encode ADTs in most mainstream languages as a closed hierarchy of classes ( bugsquash.blogspot.com/2012/01/… ) or as a catamorphism ( tonymorris.github.io/blog/posts/debut-with-a-catamorphism ). It's just more verbose.Conspiracy
S
1

What I would do is design my library in a DI container agnostic way to limit the dependency on the container as much as possible. This allows to swap out on DI container for another if need be.

Then expose the layer above the DI logic to the users of the library so that they can use whatever framework you chose through your interface. This way they can still use DI functionality that you exposed and they are free to use any other framework for their own purposes.

Allowing the users of the library to plug their own DI framework seems a bit wrong to me as it dramatically increases amount of maintenance. This also then becomes more of a plugin environment than straight DI.

Slim answered 12/1, 2010 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.