Best location for Fluent IOC configuration/Modules

2019-02-13 20:07发布

问题:

I am struggling to find the best place to locate my Ninject configuration "Modules" (the place where Type bindings are specified). I hope I am just missing some obvious trick, as this is starting to turn into a deal-breaker for me with using fluent configuration (and thus Ninject):

In a simple Web stack containing three separate projects: Web, BusinessLogic, DataAccess. I do not want the Web tier to have to directly reference the DataAccess tier, but I can't see a way around this because:

  • If I put the DataAccess configuration Module in the DataAccess layer, I have to reference DataAccess layer so I can access the configuration module when instantiating the Ninject Kernel in the Web tier

  • If I put the DataAccess configuration Module in the Web tier, I have to reference the DataAccess layer to have access to the types I want to bind

  • If I put the DataAccess configuration Module in a separate configuration project, I end up with circular reference issues when trying to specify bindings for both web and DataAccess tiers.

Part of the benefit of IOC is to allow loose coupling, but as far as I can see, use of Ninject would require me to add more direct project references that I currently have. What am I missing?

回答1:

Ninject does not require that the assemblies are referenced! You can tell the Kernel to load all modules from the assemblies that match a certain pattern - see the Load() overloads! Using this mechanism you map the can expose your features as Modules as @Daniel Marbach suggested in the place where each feature is implemented. I do not like these huge modules defining every binding for an assembly. I'd rather have each in a specific small module for a certain feature.

This also allows one to enable/disable/replace implementations without recompilation of the other assemblies (at least in case you have the interfaces in separate assemblies).

So basically you have:

  • One or more web tier assemblies: containing the controllers, views and bindings for the web tier. Each of the assembly references some assemblies that define the interfaces it depends on.
  • One or more assemblies that define the interfaces for the dependencies of the web tier.
  • One or more business logic assemblies implementing all or some of the interfaces required by the web tier. Referencing some assemblies that contain the interfaces of the objects they depend on. Containing the modules that define the bindings for the components they provide.
  • One or more assemblies that define the interfaces for the dependencies of the business logic tier.
  • One or more assemblies that implement the the dependencies of the business logic tier and possibly some of the web tier (e.g. data that is directly provided with out the business logic). Containing the modules of the components they provide.
  • One bootstrapper loading the modules of these assemblies using kernel.Load("*.dll") or similar.

The advantage of this is:

  • No reference from the web tier to the business logic tier and data tier
  • No reference from the business logic tier to the data tier
  • Each layer is replaceable without any impact on the others


回答2:

I typically create an assembly just for the IOC Container and configuration; That Assembly can then reference all of the other Assemblies and Ninject (or StructureMap in my case). Then the web application just has to reference the IOC assembly and include a couple lines of initialization code that directly use the IOC assembly.

One note however - My IOC assembly does not reference the web assembly (that would introduce a circular reference). Anything that needs to be injected is defined outside of the web assembly, so this is not a concern for me.



回答3:

The best way to organize your modules is by feature! For example

  • AuthenticationModule
  • OrderModule
  • CustomerModule

Have fun!



回答4:

I always put Ninject Modules Configuration into separate assembly like Acme.Common and reference to this from Acme.Data, Acme.Domain etc. so there is no circular dependencies, I can always replace Acme.Common after some modifications in registrations without troubles.