Hello.
When designing applications, it is good use of Dependency Injection (dependency injection). This approach allows you to make the code loosely coupled, and this in turn provides ease of maintenance. It also makes testing easier and the code becomes beautiful, versatile and replaceable. From the very beginning this principle was used in the development of our products: in both high-load DSP and corporate
Hybrid . We wrote modules, connected integration with various systems, the number of dependencies grew and at some point it became difficult to maintain the application configuration itself. In addition, implicit registrations were added (for example, the custom DependencyResolver for Web Api was set up in the Web Api settings) and difficulties began to arise with the
order of calling configuration modules. In the end, we developed an approach for registering, configuring and initializing modules in a complex application. About him and tell.

First we need to clarify that for servicing various tasks (even within the same product) we have several types of applications: services, console applications, asp.net. Accordingly, the initialization system represented its zoo everywhere, only in that there was a DependencyConfig class with a damn cloud of taste and color dependencies. Each application also had its own advanced settings. For example, setting up routing, converters, authorization filters in asp.net mvc, which should have been called after dependency registration and validation of this registration. Accordingly, the problem arose:
')
- unify configuration for different types of applications
- remove the need to set the initialization sequence
- break the registration of modules into light, isolated from each other primitives.
As a result, we have identified 3 types of elementary configurations: dependencies (dependency), initialization (init), and settings (settings, which are actually the union of the two previous ones).
Dependencies (IDependency)
Dependency is a primitive for registering, ha ha, dependencies of a single module. In general, implements the IDependency interface:
public interface IDependency<TContainer> { void Register(TContainer container); }
where TContainer is an IoC container (SimpleInjector is used here and below as an example of a container). Accordingly, the Register method registers the services of a single logic module. Other IDependency primitives can also be registered by directly calling the constructor and the Register method. Example:
public class TradingDeskDependency : IDependency<Container> { public void Register(Container container) { container.Register(() => new SwiffyClient(new SwiffyOptions{ MillisecondsTimeout = 20000 })); new DspIntegrationDependency().Register(container); } }
Initialization (IInit)
Initializations include the code that should be executed after registration and dependency checking, but before the main application logic starts. This can be asp.net mvc and web api setup or something similar. In general, the initialization class implements the IInit interface:
public interface IInit { void Init(IDependencyResolver resolver); }
Where IDependencyResolver is needed if you need to get some service from the dependencies, or to get the methods of getting dependencies themselves, as in the example:
public class AspNetMvcInit: IInit { public void Init(IDependencyResolver resolver) { System.Web.Mvc.DependencyResolver.SetResolver(resolver.GetService, resolver.GetServices); new RouteInit().Init(resolver); } }
As well as for dependencies, you can use nested initialization primitives.
Settings (ISettings)
Settings are needed if both the registration of dependencies and the initialization call after are necessary in the logic module. They are described the easiest:
public interface ISettings<TContainer> : IDependency<TContainer>, IInit { }
Accordingly, the settings represent the complete functionality for configuring the logic module: both dependency registration and additional settings.
General construction
So, we have primitives into which the configuration can be broken, it remains to configure them. For this we will be helped by the Application class, which implements the IApplication interface:
public interface IApplication<TContainer> { IApplication<TContainer> SetDependency<T>(T dependency) where T : IDependency<TContainer>; IApplication<TContainer> RemoveDependency<T>() where T : IDependency<TContainer>; IApplication<TContainer> SetInit<T>(T init) where T : IInit; IApplication<TContainer> RemoveInit<T>() where T : IInit; IApplication<TContainer> SetSettings<T>(T settings) where T : ISettings<TContainer>; IApplication<TContainer> RemoveSettings<T>() where T : ISettings<TContainer>; IAppConfig Build(); }
As you can see from the code, IApplication allows you to add all types of settings (as well as delete them). And the Build method calls the code that collects all these settings: first, dependencies are registered (+ if necessary, a check if everything is possible to register), then a code from IInit modules (and Init methods in ISettings). At the output, we get an IAppConfig object:
public interface IAppConfig { IDependencyResolver DependencyResolver { get; } IAppLogger Logger { get; } }
where DependencyResolver allows you to get services, and Logger you know why. The final code for setting up the application will be simple and transparent (although in the general case with some complications for universality):
var container = new Container() var appOptions = new AppOptions { DependencyContainer = container, GetServiceFunc = container.GetInstance, GetAllServicesFunc = container.GetAllInstances, VerifyAction = c => c.Verify(), Logger = new CustomLogger() }; var appConfig = new Application(appOptions).SetDependency(new TradingDeskDependency()) .SetInit(new AspNetMvcInit()) .Build();
The only class that will have to be defined explicitly is CustomLogger. If we want to keep track of situations when the registration of dependencies and initializations fails, then it should be set. Logger is described by the simplest interface:
public interface IAppLogger { void Error(Exception e); }
and write the implementation is not difficult.
As a result, this approach can be used for any type of application, we do not need to think about the configuration order, and in dependency management, we moved to the level of logic modules. Thus, you can screw up a bunch of settings, dependencies, initializations, while maintaining the sobriety of thought and fortitude.
I wrote a slightly simplified (but quite working and easily expandable) version of the library (Jdart.CoreApp), which can be studied or simply used:
1)
GitHub2)
Nuget .
Adapters for
1)
SimpleInjector2)
Autofac3)
Ninject4)
UnityThanks to all.