decorating_containers

Aedi, a dependency injection library.

Aedi is a dependency injection library. It does provide a set of containers that do IoC, and an interface to configure application components (structs, objects, etc.)

Aim:

The aim of library is to provide a dependency injection solution that is feature rich, easy to use, easy to learn, and easy to extend up to your needs.

Aedi does provide basic containers singleton and prototype, which can be combined into some- thing more complex. Though, on top of their behavior additional one can be built, such as a container that can be enabled/disabled, or a container that adds observer pattern. Out of the box framework does provide following decorating containers:

  • aliasing - provides aliasing functions
  • gc rigistered - registers instantiated components with gc, when other that gc allocator are used
  • subscribable - provides observable pattern on container events
  • typed - serves components based on their implemented interfaces
  • proxy - serves component proxies instead of original ones
  • deffered - provides deffered construction of components

Each decorating container listed below will be explained in detail, followed by a short explanation of what current example does.

Aliasing:

The aliasing container decorator adds ability to add aliases to existing components in container. Therefore a "foo" component can have multiple aliases "moo", "boo" etc. Each alias will resolve to same component in container. Such feature is useful when an existing component in container for compatibility reasons should have another identity as well. To use aliasing container simply wrap any container in aliasing method which will decorate existing one. UFCS syntax is desired for fluent like style.

1 auto cont = aggregate(
2     singleton, "singleton",
3     prototype, "prototype",
4     values, "parameters",
5     gasoline, "gasoline",
6     electric, "electric",
7     diesel, "diesel"
8 ).aliasing;

GcRegistered:

GC registered decorating container registers any created component with global gc available in D. Since any component can have customized memory allocation strategy through std.experimental.allocator, were one of strategy is to use garbage collector, and each component can have dependencies to other components, in some cases, gc on collection cycle could deallocate a component (which technically isn't referenced by anyone) even if it is still referenced by other component of which gc is not aware of. GC registered container aims to register all public components provided by underlying container into GC in order to avoid the case from above. To use it, just suffix any container with gcRegistered.

1 auto cont = aggregate(
2     singleton, "singleton",
3     prototype, "prototype",
4     values, "parameters",
5     gasoline, "gasoline",
6     electric, "electric",
7     diesel, "diesel"
8 ).aliasing.gcRegistered;

Subscribable:

Subscribable container offers a list of events to which a listener can subscribe and perform operations on. Currently only two events are provided:

  1. instantiation pre event - fired before instantiation of container
  2. instantiation post event - fired after instantiation of container

An example of such listeners can be like in listing below, which will enable a container before instantiation based on profile argument

1 cont.subscribable.subscribe(
2     ContainerInstantiationEventType.pre,
3     {
4         if (cont.has(cont.locate!string("profile"))) {
5             cont.locate!Switchable(cont.locate!string("profile")).enabled = true;
6             return;
7         }
8 
9         throw new Exception("No engine profile selected. Specify it with -p");
10     }
11 );

Typed:

Typed container decorates underlying container with ability to provide a component based on interface it implements. Code using typed container attempts to get a component by an interface such as Engine in this example, typed container will check if underlying container has a component identified by provided interface. If yes it will give the component from decorated container. If not, it will search for all registered components that implement the required interface and use first found component to serve it. Notice that in this example, no Engine was registered in container by Engine interface. Each instance GasolineEngine, ElectricEngine, and DieselEngine are registered by their type only. No attempt to alias one of them to Engine is done, yet when Car is instantiated, a component implementing Engine interface is supplied. This magic is done by typed container, which supplied first available component implementing Engine interface.

Proxy:

A proxy container proxies all components supplied by decorated container. It will supply proxy objects instead of real components, which in turn will on each public call of a proxy will redirect it to component in redirected container, and return a value from it. The proxy container alone is not much useful. It is intended to be used along with containers that are dependendet on some global state to provide components. An example of such containers could be containers that provide different instances of same component per thread or fiber, or in case of web application, a container that gives new instances of same component for each new available request web application receives.

Deffered:

Deffered container, executes delayed components construction and configuration. Construction and configuration of a component can occur in cases when there is a circular dependency between a set of components. In such case to resolve it, injection of one dependency can be delayed. Delaying of construction or configuration is left to component factory, while execution of delayed action is enforced upon Deffered container. To enable circular dependency resolution, decorate container with deffered decorator, and register components using withConfigurationDefferring configuration context (simply append it after container.configure method). This exampe shows a typical example of circular dependency. A car has a dependency on four tires, while each tire has a dependency on a car instance, resulting in a circular dependency. Removing deffered or withConfigurationDefferring will result in circular dependency exception thrown by container.

Members

Classes

Car
class Car

A class representing a car.

DieselEngine
class DieselEngine

A concrete implementation of Engine that uses diesel for propelling.

ElectricEngine
class ElectricEngine

A concrete implementation of Engine that uses electricity for propelling.

GasolineEngine
class GasolineEngine

A concrete implementation of Engine that uses gasoline for propelling.

Tire
class Tire

Tire, what it can represent else?

Interfaces

Engine
interface Engine

Interface for engines.

Structs

Color
struct Color

A struct that should be managed by container.

Size
struct Size

Size of a car.

Examples

Such behavior can be useful, like in car company example, that at certian point of time decided that their application should provide upon start cars with different engines, electric/diesel/gasoline.

The workflow needed to implement in application in order to allow 3 different configurations is shown below and consists of following steps:

  1. read profile argument from command line or environment
  2. switch on it, and register diesel/gasoline/electric engine by Engine interface depending on selected profile

Following snippet of code shows all decorating containers in use except of proxy one.

1 auto decorated() {
2     auto gasoline = singleton.typed.switchable.enabled(false);
3     auto electric = singleton.typed.switchable.enabled(false);
4     auto diesel = singleton.typed.switchable.enabled(false);
5 
6     auto cont = aggregate(
7         singleton, "singleton",
8         prototype, "prototype",
9         values, "parameters",
10         gasoline, "gasoline",
11         electric, "electric",
12         diesel, "diesel"
13     ).aliasing.gcRegistered.deffered;
14 
15     return cont.subscribable.subscribe(
16         ContainerInstantiationEventType.pre,
17         {
18             if (cont.has(cont.locate!string("profile"))) {
19                 cont.locate!Switchable(cont.locate!string("profile")).enabled = true;
20                 return;
21             }
22 
23             throw new Exception("No engine profile selected. Specify it with -p");
24         }
25     );
26 }

The profile based container is assembled from 3 typed and switchable containers, and a subscribable composite container with gc component registration and construction of deffered components. When the application is booted up, the code from main(string[] args) loads into container "profile" argument. Afterwards components are registered into container, and for each profile, the right engine is registered in respective gasoline, electric, diesel containers. Once this is finished, the container is instantiated using intantiate method. During instantiation phase, subscribable composite container fires an pre-instantiation event on which, a delegate is attached, that checks for "profile" argument, and enables the container identified by value in profile container. Afterwards construction of components proceeds. When car is constructed typed container jumps in and provides an implenentation of Engine for car depending on enabled container. When construction arrives at a Tire, a circular dependency is detected, and construction of a component is deffered at later stage in order to break dependency chain. Once component is constructed, it is registered with global gc instance in order to avoid destruction of gc managed components while they are still referenced by non-managed components. Once instantiation is finished, car is fetched from container and displayed in console.

Try running this example, pass as argument --profile with value of gasoline, electric, diesel. Experiment with it, to understand decorating containers.

Meta

Authors

aermicioi

License

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:

The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.