Skip to content

Dependency injection

padzikm edited this page Dec 12, 2015 · 6 revisions

We want to develop each service independently, so with that comes transparent dependency integration. Each service can for example use the same message bus technology, but configured differently, possible with different lifetime. In this project CastleWindsor container is used, but you can use any other in your project.
Solution to this comes with looking at responsibilities of main web application and each service. Main app provides configuration, publishes incoming requests and gathers responses, provides public api and navigates user through it. It is also great place for cross cutting concerns like authentication, authorization, logging, etc. Rendering part of main app only provides grids and templates that render content to a user. Services on the other hand provide all needed data and behavior on a page and actually handle incoming requests, each of them in its way. So to sum up each service is different from any other and all services have different responsiblities from main application, what is more each controller in whole application is unique - as there are no common controllers - and it's a controller that is starting point of resolving dependency graph. As a result each component can have its own dependency container.

For whole application to work we have to integrate different containers, because resolving dependency graph for each request usually starts with creating controller instance and because each container is responsible for creating controllers from its service, they all must be integrated at this level. We also need to manage lifetime of every dependency, so each component must be integrated also at this level. For both of these requirements we can expect in main application's container that each service will deliver for example implementation of IControllerActivator - that will be responsible for creating controller and resolving its dependency graph, as there are no common controllers - and implementation of IControllerFactory for releasing each controller and managing lifetime of its dependencies.
Example of integrating containers for creating controllers:

public class WindsorControllerActivatorDecorator : IControllerActivator
    {
        private readonly IKernel _kernel;
        private readonly IEnumerable<IControllerActivator> _activators;

        public WindsorControllerActivatorDecorator(IKernel kernel, IEnumerable<IControllerActivator> activators)
        {
            _kernel = kernel;
            _activators = activators;
        }

        public IController Create(RequestContext requestContext, Type controllerType)
        {
            if (_kernel.HasComponent(controllerType))
                return _kernel.Resolve(controllerType) as IController;

            foreach (var activator in _activators)
            {
                var controller = activator.Create(requestContext, controllerType); //only one container can create given type
                if (controller != null)
                    return controller;
            }
            return null;
        }
    }

Example of integrating containers for releasing controllers:

public class WindsorControllerFactoryDecorator : DefaultControllerFactory
    {
        private readonly IKernel _kernel;
        private readonly IEnumerable<IControllerFactory> _factories;

        public WindsorControllerFactoryDecorator(IKernel kernel, IEnumerable<IControllerFactory> factories)
        {
            _kernel = kernel;
            _factories = factories;
        }


        public override IController CreateController(RequestContext requestContext, string controllerName)
        {
            var c = base.CreateController(requestContext, controllerName);
            return c;
        }

        public override void ReleaseController(IController controller)
        {
            _kernel.ReleaseComponent(controller);
            foreach (var factory in _factories) //only one container can release given controller
                factory.ReleaseController(controller);
        }
    }

There's now issue how to deliver this implementations to main app's container. For doing this we can define in web.config of main app to load each service's web application when main application starts:

<system.web>
    <compilation debug="true" targetFramework="4.5">
      <assemblies>
        <add assembly="CompositeUI.OneService.Web" /> //load this assembly on app start
        <add assembly="CompositeUI.SecondService.Web" />
      </assemblies>
    </compilation>
    <httpRuntime targetFramework="4.5" />
</system.web>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="bin\OneService;bin\SecondService" /> //look for assemblies also in these folders
</assemblyBinding>

Now when main app's container is created we want to tell services apps to configure themselves and provide needed infrastrucre to main container. In order of doing so, we define abstract ServiceWindsorInstaller class, that will implement standard IWindsorInstaller interface, so we can extract instance of this class from each service app and execute it as normal dependency installer for main app container. Execution of these objects will be signal for each executing service, that app has started and it's time to configure its own container and provide configuration to main one:

public abstract class ServiceWindsorInstaller : IWindsorInstaller
    {
        protected readonly string _binPath; //bin path of main application

        public ServiceWindsorInstaller(string binPath)
        {
            _binPath = binPath;
        }

        public abstract void Install(IWindsorContainer container, IConfigurationStore store);
    }

public class WindsorBootstraper : ServiceWindsorInstaller //private service container
    {
        private static volatile IWindsorContainer _container;
        private static readonly object _lockObject = new object();

        internal static IWindsorContainer Container { get { return _container; } }

        public WindsorBootstraper(string binPath) : base(binPath) { }

        public override void Install(IWindsorContainer container, IConfigurationStore store) //signal that app has started
        {
            var path = GetProjectPath();
            ConfigureContainer(path); //configure service container
            container.Install(new ExternalWindsorInstaller(path, _container)); //provide infrastructure needed for main app
        }

Infrastructure needed in main app for integrating service with others, apart from resolving and releasing controllers, contains view engine and request handler. All other components are optional - as for example IApplicationConfiguration that have two methods to call when app starts and when it is closed in order to for instance release service container.
As this solution doesn't rely on features of specific container, you can use it with any dependency container you like.

Clone this wiki locally