Search code examples
c#dependency-injectionsimple-injectorservice-locator

Moving from Service Locator to Dependency Injection


I prepared sample application based on which I would like to discuss moving to Dependency Injection instead of Service Locator. I am pretty new in DI so please be patient with me. Sample app is written with Simple Injector as DI library. Registrations in Bootstrapper works as expected. I have singleton for IMessageBox interface and new instance ComputationCores each time I need it.

I read some articles about DI and so I know that there should be some Composition root and how it should be all working. But I found just very basic examples without real-word complexity.

Sample code:

public class DependencyResolver
{

    public static Func<Type, object> ResolveMe;

    public static T GetInstance<T>() where T : class
    {
        return (T)ResolveMe(typeof (T));
    }
}

public interface IMessageBox
{
    void ShowMessage(string message);
}

public class StandardMessageBox : IMessageBox
{
    public StandardMessageBox()
    {
        Console.WriteLine("StandardMessageBox constructor called...");
    }

    ~StandardMessageBox()
    {
        Console.WriteLine("StandardMessageBox destructor called...");
    }

    public void ShowMessage(string message)
    {
        Console.WriteLine(message);
    }
}

public interface IComputationCoreAlpha
{
    int RunComputation(int myParam);
}

public class SyncComputationCoreAlpha : IComputationCoreAlpha
{
    public SyncComputationCoreAlpha()
    {
        Console.WriteLine("SyncComputationCoreAlpha constructor called...");
    }

    ~SyncComputationCoreAlpha()
    {
        Console.WriteLine("SyncComputationCoreAlpha destructor called...");
    }

    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public class AsyncComputationCoreAlpha : IComputationCoreAlpha
{
    public AsyncComputationCoreAlpha()
    {
        Console.WriteLine("AsyncComputationCoreAlpha constructor called...");
    }

    ~AsyncComputationCoreAlpha()
    {
        Console.WriteLine("AsyncComputationCoreAlpha destructor called...");
    }


    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public interface IComputationCoreBeta
{
    int RunComputation(int myParam);
}

public class SyncComputationCoreBeta : IComputationCoreBeta
{
    public SyncComputationCoreBeta()
    {
        Console.WriteLine("SyncComputationCoreBeta constructor called...");
    }

    ~SyncComputationCoreBeta()
    {
        Console.WriteLine("SyncComputationCoreBeta destructor called...");
    }


    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public class AsyncComputationCoreBeta : IComputationCoreBeta
{
    public AsyncComputationCoreBeta()
    {
        Console.WriteLine("AsyncComputationCoreBeta constructor called...");
    }

    ~AsyncComputationCoreBeta()
    {
        Console.WriteLine("AsyncComputationCoreBeta destructor called...");
    }

    public int RunComputation(int myParam)
    {
        return myParam * myParam;
    }
}

public interface IProjectSubPart
{
    int DoCalculations(int myParam);
}

public class ProjectSubPart1 : IProjectSubPart
{
    public int DoCalculations(int myParam)
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Hardly working 1...");
        var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
        var ccB = DependencyResolver.GetInstance<IComputationCoreAlpha>();

        return ccA.RunComputation(myParam) + ccB.RunComputation(myParam + 1);
    }
}

public class ProjectSubPart2 : IProjectSubPart
{
    public int DoCalculations(int myParam)
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Hardly working 2...");
        var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();

        return ccA.RunComputation(myParam * 3);
    }
}

public class ProjectSubPartN : IProjectSubPart
{
    public int DoCalculations(int myParam)
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Hardly working N...");
        return -3;
    }
}

public class ManhattanProject
{
    public void RunProject()
    {
        var messageBoxService = DependencyResolver.GetInstance<IMessageBox>();
        messageBoxService.ShowMessage("Project started...");
        var subPart1 = new ProjectSubPart1();
        var subPart2 = new ProjectSubPart2();
        var subPartN = new ProjectSubPartN();

        var result = subPart1.DoCalculations(1) + subPart2.DoCalculations(2) + subPartN.DoCalculations(3);

        messageBoxService.ShowMessage(string.Format("Project finished with magic result {0}", result));
    }
}

public class Sample
{
    public void Run()
    {
        BootStrapper();

        var mp = DependencyResolver.GetInstance<ManhattanProject>();
        mp.RunProject();

    }

    private void BootStrapper()
    {
        var container = new Container();
        container.RegisterSingle<IMessageBox, StandardMessageBox>();
        container.Register<IComputationCoreAlpha, SyncComputationCoreAlpha>();
        container.Register<IComputationCoreBeta, AsyncComputationCoreBeta>();

        DependencyResolver.ResolveMe = container.GetInstance;
    }

}

In DI it is allowed to call Container.GetInstance (resolve method) just in Composition root, nowhere else. Majority of dependencies should be injected in constructors.

Q1: If I would move to DI I suppose that constructor of ManhattanProject should looks something like this: ManhattanProject(IMessageBox mb, IComputationCoreAlpha cca, IComputationCoreBeta ccb). But this would lead to having one instance per mb, cca, ccb. And not everytime new instance of cca, ccb on my demands.

Q1a: I suppose this could be solved by some kind of abstract factory for cca, ccb which could give me new instance per request. But then - what is the purpose of BootStrapper?

Q2: ManhattanProject can consist from more ProjectSubParts which use different coputationCores - e.g. 42. Thus it is totaly inappropriate to use constructor injection in this way (to provide Computation cores) and some kind of facade should be used. Since facade should have limited count of args in constructor too I would end up with many and many nested facades. I suppose this is wrong.

Q3: I am using ProjectSubParts which allows me to do some work. All inherits from IProjectSubPart interace. In case I would want to inject different implementations for different ProjectSubParts how should I do that? Should I create new interface for each ProjectSubpart to allow DI container to resolve which implementation use?

Q4: Based on provided sample (and Service Locator pattern) it would be very easy for me to create instance of IComputationCoreAlpha which could internally create new and clean inner object by calling DependencyResolver.GetInstance everytime I need. Moreover I would have full control over them and I could call Dispose on them when I would be done with their usage. If in DI concept whole graph would be created in CompositionRoot how would be this kind of usage possible?

Thanks


Solution

  • Q1: If I would move to DI I suppose that constructor of ManhattanProject should looks something like this: ManhattanProject(IMessageBox mb, IComputationCoreAlpha cca, IComputationCoreBeta ccb).

    Classes should only depend on services that they need directly. So the ManhattanProject should not depend on any computation core, but simply on the IProjectSubPart abstraction.

    Q1a: I suppose this could be solved by some kind of abstract factory for cca, ccb which could give me new instance per request. But then - what is the purpose of BootStrapper?

    The purpose of the bootstrapper / composition root is to build up the object graphs. If you create a factory abstraction, it needs to be implemented somewhere. This 'somewhere' is your composition root. The factory implementation should be INSIDE your composition root.

    Besides using a factory, a better approach is to inject an IEnumerable<IProjectSubPart>. Your ManhattanProject will in that case look as follows:

    public class ManhattanProject
    {
        private readonly IMessageBox messageBoxService;
        private readonly IEnumerable<IProjectSubPart> parts;
    
        public ManhattanProject(IMessageBox messageBoxService, 
            IEnumerable<IProjectSubPart> parts) {
            this.messageBoxService = messageBoxService;
            this.parts = parts;
        }
    
        public void RunProject() {
            messageBoxService.ShowMessage("Project started...");
    
            var calculationResults =
                from pair in parts.Select((part, index) => new { part, value = index + 1 })
                select pair.part.DoCalculations(pair.value);
    
            var result = calculationResults.Sum();
    
            messageBoxService.ShowMessage(
                string.Format("Project finished with magic result {0}", result));
        }
    }
    

    When you depend upon an IEnumerable<IProjectSubPart> you can prevent the ManhattanProject from changing everytime a new IProjectSubPart implementation is added to the system. In Simple Injector you can register this as follows:

    // Simple Injector v3.x
    container.RegisterSingleton<IMessageBox, StandardMessageBox>();
    container.Register<ManhattanProject>();
    container.RegisterCollection<IProjectSubPart>(new[] {
        typeof(ProjectSubPart1),
        typeof(ProjectSubPart2),
        typeof(ProjectSubPartN)
    });
    
    // Simple Injector v2.x
    container.RegisterSingle<IMessageBox, StandardMessageBox>();
    container.Register<ManhattanProject>();
    container.RegisterAll<IProjectSubPart>(new[] {
        typeof(ProjectSubPart1),
        typeof(ProjectSubPart2),
        typeof(ProjectSubPartN)
    });
    

    In general you would even shield other parts of the application from having to know that there are multiple implementations of a certain abstraction, but hiding this doesn't seem possible in your case, because it is the ManhattanProject that is (currently) responsible of providing a different value to each IProjectSubPart. If it would be possible however, the right solution would be to let the ManhattanProject depend upon IProjectSubPart directly instead of depending upon IEnumerable<IProjectSubPart> and you would let the Composition Root inject an composite implementation that would wrap the IEnumerable<IProjectSubPart> as described here.

    The same pattern can be applied to all the IProjectSubPart implementations. For instance:

    public class ProjectSubPart1 : IProjectSubPart
    {
        private readonly IMessageBox messageBoxService;
        private readonly IEnumerable<IComputationCoreAlpha> computers;
    
        public ProjectSubPart1(IMessageBox messageBoxService,
            IEnumerable<IComputationCoreAlpha> computers) {
            this.messageBoxService = messageBoxService;
            this.computers = computers;
        }
    
        public int DoCalculations(int myParam) {
            messageBoxService.ShowMessage("Hardly working 1...");
    
            var calculationResults =
                from pair in computers.Select((computer, index) => new { computer, index })
                select pair.computer.RunComputation(myParam + pair.index);
    
            return calculationResults.Sum();
        }
    }
    

    These IComputationCoreAlpha implementations can be registered as collection as follows:

    container.RegisterCollection<IComputationCoreAlpha>(new[] {
        typeof(SyncComputationCoreAlpha),
        typeof(AsyncComputationCoreAlpha)
    });
    

    Q2: Since facade should have limited count of args in constructor too I would end up with many and many nested facades.

    It's hard to say anything useful about this. Probably my given implementations with LINQ queries won't work in your case, but your examples are too broad to be very specific about this. In the end you might want to have a custom abstraction, but I'm not sure yet.

    Q3: I am using ProjectSubParts which allows me to do some work. All inherits from IProjectSubPart interface. In case I would want to inject different implementations for different ProjectSubParts how should I do that? Should I create new interface for each ProjectSubpart to allow DI container to resolve which implementation use?

    That highly depends on your design. We should take a look at the Liskov Substitution Principle for this, which basically says that any subtype of a given abstraction should behave in a way that is compatible with the abstraction. So in your case, if a certain class expects a certain IProjectSubPart implementation, and won't function correctly with a different implementation, that means you are breaking the Liskov Substitution Principle. This means that those implementations do not behave the same, even though they might have the exact method signatures. In that case you should split them up in multiple interfaces.

    If a consumer however still functions correctly and changing implementations is just some convenience, than it's okay to let them have the same abstraction. Good example is an ILogger abstraction with a FileLogger and MailLogger implementation. In some parts of the system, you might decide that it is important to send messages through mail. Still for the class that depends on ILogger, it will function the same whether or not the messages are written to file, sent through mail, or not sent at all.

    Whether or not you are violating LSK is up to you to find out.

    Q4: Based on provided sample (and Service Locator pattern) it would be very easy for me to create instance of IComputationCoreAlpha which could internally create new and clean inner object by calling DependencyResolver.GetInstance everytime I need. Moreover I would have full control over them and I could call Dispose on them when I would be done with their usage. If in DI concept whole graph would be created in CompositionRoot how would be this kind of usage possible?

    I would say that DI actually makes this job easier. For instance, let's try to implement what you want using service locator:

    public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
    {
        public int RunComputation(int myParam) {    
            var heavyWeight = DependencyResolver.GetInstance<IComputationCoreAlpha>();
            return heavyWeight.RunComputation(myParam);
        }
    }
    

    This is a proxy class that is able to lazily create the real instance when the RunComputation is called. But this actually presents us with a problem. This becomes clear if we look at how the consumer will use this:

    public int DoCalculations(int myParam) {
        var ccA = DependencyResolver.GetInstance<IComputationCoreAlpha>();
        return ccA.RunComputation(myParam);
    }
    

    Here the DoCalculations method resolves the IComputationCoreAlpha from the service locator. This returns us the LazyComputationCoreAlphaProxy instance (since we registered that in our locator). After resolving it we will call RunComputation on it. But inside the RunComputation we resolve IComputationCoreAlpha again. We would like to resolve IComputationCoreAlpha, because otherwise our LazyComputationCoreAlphaProxy needs to depend directly on a different implementation, but this would cause a violation of the Dependency Inversion Principle, and will likely cause us to have many different LazyComputationCoreAlphaProxys. One for each implementation.

    But if we try to resolve IComputationCoreAlpha here, the locator will return us the LazyComputationCoreAlphaProxy again, and this will eventually result in a stack overflow exception.

    Now let's take a look at how this looks with Dependency Injection:

    public class LazyComputationCoreAlphaProxy : IComputationCoreAlpha
    {
        private readonly Func<IComputationCoreAlpha> factory;
    
        public LazyComputationCoreAlphaProxy(Func<IComputationCoreAlpha> factory) {
            this.factory = factory;
        }
    
        public int RunComputation(int myParam) {    
            var heavyWeight = this.factory.Invoke();
            return heavyWeight.RunComputation(myParam);
        }
    }
    

    Here we inject a Func factory into the LazyComputationCoreAlphaProxys constructor. This allows the proxy to stay oblivious to the actual type it creates, while still allowing the same lazy behavior as before. Now we delegated the responsibility of building up that part of the object graph again to our composition root. We could wire this manually as follows:

    LazyComputationCoreAlphaProxy(() => new SyncComputationCoreAlpha())
    

    Or we can use the decorator facility of Simple Injector to do this for us:

    container.RegisterCollection<IComputationCoreAlpha>(new[] {
        typeof(SyncComputationCoreAlpha),
        typeof(AsyncComputationCoreAlpha)
    });
    
    container.RegisterDecorator(
        typeof(IComputationCoreAlpha), 
        typeof(LazyComputationCoreAlphaProxy));
    

    With the RegisterDecorator registration, Simple Injector will automatically wrap any IComputationCoreAlpha implementation in a LazyComputationCoreAlphaProxy decorator. Out of the box, Simple Injector understands Func factory delegates inside decotators and it will ensure that a factory is injected that created the decorated object.

    But since we're on the subject of decorators now. Dependency Injection with decorators gives us even more possibilities to improve the code. For instance, much of the code in the IProjectSubPart look alike. They all have the same message box logging code:

    public class ProjectSubPart1 : IProjectSubPart
    {
        private readonly IMessageBox messageBoxService;
        private readonly IEnumerable<IComputationCoreAlpha> computers;
    
        public ProjectSubPart1(IMessageBox messageBoxService,
            IEnumerable<IComputationCoreAlpha> computers) {
            this.messageBoxService = messageBoxService;
            this.computers = computers;
        }
    
        public int DoCalculations(int myParam) {
            messageBoxService.ShowMessage("Hardly working 1...");
    
            // part specific calculation
        }
    }
    

    If you have many different IProjectSubPart, this is a lot of duplicate code that not only complicates the actual implementations, but also needs to be maintained. What can move that infrastructural concern (or cross-cutting concern) out of those classes, and implement it just once: in a decorator:

    public class MessageBoxLoggingProjectSubPart : IProjectSubPart
    {
        private readonly IMessageBox messageBoxService;
        private readonly IProjectSubPart decoratee;
    
        public MessageBoxLoggingProjectSubPart(IMessageBox messageBoxService,
            IProjectSubPart decoratee) {
            this.messageBoxService = messageBoxService;
            this.decoratee = decoratee;
        }
    
        public int DoCalculations(int myParam) {
            messageBoxService.ShowMessage("Hardly working 1...");
    
            return this.decoratee.DoCalculations(myParam);
        }
    }
    

    With this decorator, you can simplify the parts to the following:

    public class ProjectSubPart1 : IProjectSubPart
    {
        private readonly IEnumerable<IComputationCoreAlpha> computers;
    
        public ProjectSubPart1(IEnumerable<IComputationCoreAlpha> computers) {
            this.computers = computers;
        }
    
        public int DoCalculations(int myParam) {
            var calculationResults =
                from pair in computers.Select((computer, index) => new { computer, index })
                select pair.computer.RunComputation(myParam + pair.index);
    
            return calculationResults.Sum();
        }
    }
    

    Notice how the ProjectSubPart1 doesn't need to take a dependency upon IMessageBox anymore. This cleans up the implementation (and don't forget about the 42 other implementations you have). Again, if we would create such part manually we would do it as follows:

    new MessageBoxLoggingProjectSubPart(new ProjectSubPart1(computers));
    

    With Simple Injector however, this becomes even easier:

    container.RegisterCollection<IProjectSubPart>(new[] {
        typeof(ProjectSubPart1),
        typeof(ProjectSubPart2),
        typeof(ProjectSubPartN)
    });
    
    container.RegisterDecorator(
        typeof(IProjectSubPart), 
        typeof(MessageBoxLoggingProjectSubPart));   
    

    Now any time you want to change the way that things are logged, you only need to change MessageBoxLoggingProjectSubPart. For instance when you want to log after the operation has finished, or in case an exception is thrown. This prevents you from having to make sweeping changes throughout the application (which as what the Open/closed Principle is all about).

    I'm sorry for this long post. Here are some injected potatoes:

    enter image description here