Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to locate Ninject modules in a multi-tier application

My application includes a number of back-end assemblies (including an Entity Framework data repository layer) that are shared by a number of front-end assemblies (including a Windows service and an MVC3 web application).

My understanding of the Ninject binding process is that each assembly that contains injectable types should also contain an Ninject module that defines the default bindings for these types. The set of defined modules would then be loaded into the Ninject Kernel of the consuming assemblies.

However, I am running into problems, since the required binding scope is not always consistent. For example, my MVC project needs to bind to the data context InRequestScope, whereas the Windows service binds to the same class InThreadScope.

I can obviously solve this problem by relocating all the modules into the front-end projects and thus maintain separate copies of each module for each usage scenario, but this seems hacky, since it duplicates much of the module content across multiple projects.

Is there a best practice about where modules should be located in a multi-tier application and how can I reconcile this with my need for binding differences between projects?

Many thanks for your suggestions,

Tim

like image 249
Tim Coulter Avatar asked Aug 16 '12 10:08

Tim Coulter


2 Answers

For a solution with a single application, the general advice is to register your container in the application project (your web app, or web service project). For a web application this would typically be the Global.asax Application_Start. This place where you wire everything together is called the Composition Root in DI terminology.

With a multi-application solution, you would still have a single composition root per application project. This has to be, since every application has its unique configuration. On the other hand, duplicated code is always bad. You don't want to have to change three places when you introduce a new abstraction.

The trick is to move all registrations down the project hierarchy. For instance you can define a single 'bootstrap assembly' that depends on your business layer assemblies (and below) and let it have all the registrations for those assemblies that don't change. The composition roots of the applications can then use that assembly to get the default registrations and extend it with the application specific dependencies.

Such a thing might look like this:

// MVC Composition root
public static void Bootstrap()
{
    var container = new Container();

    // Default registrations
    BusinessLayerBootstrapper.Bootstrap(container);

    // Application specific registrations
    container.Bind<IUserContext>().To<AspNetUserContext>();

    DependencyResolver.Current = 
        new ContainerDependencyResolver(container);
}

// Windows Service Composition root
public static void Bootstrap()
{
    var container = new Container();

    // Default registrations
    BusinessLayerBootstrapper.Bootstrap(container);

    // Application specific registrations
    container.Bind<IUserContext>().To<SystemUserContext>()
        .SingleScoped();

    // Store somewhere.
    Bootstrapper.Container = container;
}

// In the BL bootstrap assembly
public static class BusinessLayerBootstrapper
{
    public static void Bootstrap(Container container)
    {
        container.Bind<IDepenency>().To<RealThing>();
        // etc
    }
}

Although you don't need to have a separate bootstrapper assembly (you can place this code in the BL itself), this allows you to keep your business layer assemblies free from any dependencies to your container.

Also note that I'm just calling a static Bootstrap() method, instead of using (Ninject) Modules. I tried to keep my answer independent of the framework, since your question is general and the advice will be the same for all DI frameworks. However, of course you can use the Ninject module feature if you want.

like image 57
Steven Avatar answered Oct 22 '22 07:10

Steven


Regarding scoping a MVC application needs to have a different CompositionRoot than a windows service. I suggest you try to organize as much as possible by feature modules (for those parts which are application agnostic) and all the other bindings directly in the CompositionRoot of the MVC or WindowsService project.

Another very good approach is to define a common set of conventions which helps you to express the most binding concerns in a few lines. Therefore your apps could have the following to bindings:

MVC App

Bind(c => c.FromAssemblyContaining<IRepository>()
           .SelectAllClasses()
           .InheritedFrom<IRepository>()
           .Configure(b => b.InRequestScope()));

Your Windows Service App

Bind(c => c.FromAssemblyContaining<IRepository>()
           .SelectAllClasses()
           .InheritedFrom<IRepository>()
           .Configure(b => b.InThreadScope()));

In my point of view combined with a feature oriented structuring the convention approach is the cleanest one.

like image 37
Daniel Marbach Avatar answered Oct 22 '22 07:10

Daniel Marbach