Dependency Container
Dependency Injection
UDA to mark fields as dependency for Field Assignment Constructor Application.
Generates a constructor with a parameter for each @dependency field and assigns the passed value to the corresponding field.
Determines why a type T is not constructable by the DI framework.
Generates code for a constructor with a parameter for each @dependency field and assigns the passed value to the corresponding field.
Determines whether a type T is constructable by the DI framework.
Determines whether a type T is denied for user registration.
Determines whether a type T is supported to be used as dependency by the framework.
Determines whether a type T is applicable for user registration.
How does it work?
Dependency instances only need to be created once and can be used for all other dependent objects. As there is only one object of each type, these instances can be called “singletons”.
They can be stored in one big repository, the dependency container, and retrieved later as needed.
One of the most useful ways to specify the dependencies of a type (as in class or struct) is to declare a constructor with them as parameters.
// Example: `LoginService` depends on `PasswordHashUtil`, `DatabaseClient` and `Logger`. class LoginService { public this( PasswordHashUtil passwordHashUtil, DatabaseClient databaseClient, Logger logger, ) { // … } }
Getting the dependencies from the container into the service object is where the DI framework comes in. While the user could manually retrieve them from the container and pass them to the constructor (i.e. new LoginService( container.get!PasswordHashUtil(), container.get!Logger(), container.get!DatabaseClient() ), this would get tedious quickly. The framework comes to the resuce.
auto di = new DI(); // … LoginService service = di.resolve!LoginService();
But where did the PasswordHashUtil, the Logger and the DatabaseClient come from? How did they get into the DI container?
Let’s assume the PasswordHashUtil has zero dependencies itself – it is a self-contained service class. Its constructor has either no parameters or isn’t declared explicitly (and it uses the implicit default constructor).
The DI framework can easily construct a singleton instance (of the dependency type) on the fly. This happens automatically the first time one is needed.
class PasswordHashUtil { public string hash(string password) { /* … */ } public bool verify(string hash, string userPassword) { /* … */ } }
In case a user-provided PasswordHashUtil instance has been registered in advance, the framework will skip the construction and use that one instead.
Aka dependencies of a dependency.
The 2nd dependency of the aforementioned LoginService is Logger. For illustration purposes, we’ll assume the Logger class depends on Formatter, a type that implements formatting facilities. Logger is dependency-less class (like PasswordHashUtil was).
The DI framework will retrieve a Formatter before it can construct the Logger. Since Formatter has not dependencies, the framework can construct it as mentioned in the previous chapter. If it there is one in the dependency container already, it will be used instead (and the construction will be skipped).
The next step is to construct a Logger which receives the Formatter through its constructor.
class Logger { public this(Formatter formatter) { // … } public void log(string message) { /* … */ } }
In case a user-provided Logger instance has been registered in advance, the framework will skip all construction steps and use that one instead.
This has no impact on a direct dependencies of Logger. If Logger itself depended on Formatter, it would still receive the one from the dependency container. Which in turn would lead to LoginService.formatter being different to Logger.formatter.
Logically, this distinction would be no longer relevant, if the user registered their Formatter instance with the framework in addition to their custom Logger one.
Unlike the dependencies shown in the previous two chapters, however, the DatabaseClient doesn’t depend on a bunch of other services. Instead it is constructed from four strings (socket, username, password, database name).
While the injection of a DatabaseClient into dependent services principally works like before, the framework cannot instantiate a new one by itself. Hence it is that an instance has to be registered with the framework in advance. A user-created DatabaseClient can provided by passing it to register().
The framework will pick up on it later, when it constructs LoginService or, failing that, if no custom instance has been registered beforehand, crash the program as soon as it falls back to instantiating one on its own.
class DatabaseClient { public this( string socket, string username, string password, string databaseName, ) { // … } public Statement prepare(string sql) { /* … */ } public void disconnect() { /* … */ } } // Construct a DatabaseClient and register it with the DI framework. auto databaseClient = new DatabaseClient(cfg.socket, cfg,username, cfg.password, cfg.dbName); di.register(databaseClient);
This chapter deals with solutions to overcome the conventions of the framework. (Malicious gossip has it that these are “design limitations”. Don’t listen to them…)
Non-singleton objects are sometimes also called “transient” or “prototype”.
By default, the framework will instantiate a singleton instance for each dependency type and use it for all dependent objects.
When this behavior is not desired, it’s recommended to instantiate a new object in the constructor. Check out makeNew!(T).
public this( DI di, ) { this.dependency = di.makeNew!Dependency(); }
Alternatively, one could also create a factory type and use that as a dependency instead.
The recommended way to inject primitives types (like integers, booleans, floating-point numbers, or enums) or other unsupported types (e.g. strings, other arrays, associative arrays, class pointers or pointers in general) is to wrap them in a struct.
For pointers, deferencing them might also be an option.
class Dependency { } class Foo { private Dependency d; public this(Dependency d) { this.d = d; } } // Bootstrap the DI framework. auto di = new DI(); // Then let it resolve the whole dependency tree // and construct dependencies as needed. Foo foo = di.resolve!Foo(); // The DI framework has constructed a new instance of `Dependency` // and supplied it to the constructor of `Foo`. assert(foo.d !is null);
How about types the framework cannot construct on its own?… or instaces that have been constructed in before and could be reused?
class Dependency { private int number; public this(int number) { this.number = number; } } class Foo { private Dependency d; public this(Dependency d) { this.d = d; } } // Construct an instance of the dependency manually. // Then register it with the framework. auto dep = new Dependency(128); di.register(dep); // alternative syntax variants (They all come down to the same thing.): di.register!Dependency(dep); di.register!Dependency = dep; // Resolve dependencies of `Foo` and create instance. Foo foo = di.resolve!Foo(); // The DI framework constructed a new instance of `Dependency` // and supplied it to the constructor of `Foo`. assert(foo.d !is null);
It’s really straightforward: Tell the framework which implementation type to use for dependencies of an interface type.
interface Logger { void log(string message); } class StdLogger : Logger { private int lines = 0; void log(string message) { ++lines; // …writeln(message);… } } class Service { private Logger logger; this(Logger logger) { this.logger = logger; } void doSomething() { this.logger.log("Did something."); } } // Register `StdLogger` as the implementation to construct for the // `Logger` interface. di.registerInterface!(Logger, StdLogger); // Now the framework is set up to // construct an instance of the `Service` class. Service service1 = di.resolve!Service(); // Our service instance is using the `StdLogger` implementation // that has been registered a few lines above. service1.doSomething(); service1.doSomething(); assert(di.resolve!StdLogger().lines == 2);
What if we had a type with a complex constructor, one that the framework cannot instantiate on its own?
interface Logger { void log(string message); } class FileLogger : Logger { private int lines = 0; this(string logFilePath) { // … } void log(string message) { ++lines; // … } } class Service { private Logger logger; this(Logger logger) { this.logger = logger; } void doSomething() { this.logger.log("Did something."); } } // Easy. Construct one and register it with the framework. auto fileLogger = new FileLogger("/dev/null"); di.registerInterface!Logger(fileLogger); // Now the framework is set up to // retrieve an instance of the `Service` class. Service service2 = di.resolve!Service(); // Just for the record: // The file logger starts with a line count of `0`. assert(fileLogger.lines == 0); // Let’s use the service and see whether the supplied logger is used. service2.doSomething(); assert(fileLogger.lines == 1); // alright!
> All that typing gets tedious quickly, doesn’t it?
The framework can generate all the constructor boilerplate. It’s as easy as:
class Dependency1 { } class Dependency2 { } class Foo { // Mark dependencies with the attribute `@dependency`: private @dependency { Dependency1 d1; Dependency2 d2; } // Let the framework generate the constructor: mixin DIConstructor; } auto di = new DI(); Foo foo = di.resolve!Foo(); // It works: assert(foo.d1 !is null); assert(foo.d2 !is null);
Lightweight Dependency Injection (DI) framework