Module annotation_configuration

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.

Code api, offers a rich set of functionality, and flexibility. Though alternatives to configuring a container with components should be provided along with their strengths and disadvantages. For this purpose as an alternative to code api, the framework implements annotation based configuration of components.

The main idea of annotation based configuration of components is that, information about component's dependencies and configuration properties are stored in form of annotations right in it. The framework does provide annotation based counterparts of almost all configuration primitives available in code api. Example below shows a component configured using annotation based information:

@component // Mark component to be registered in container
@qualifier("custom.identity") // Specify custom identity for component in container instead of it's type as identity
class Car {

    // ...

    public {

        @constructor(lref!Size, lref!Engine) // Construct this component using arguments passed to constructor annotation
        this(Size size, Engine engine) {
            this.size_ = size;
            this.engine = engine;
        }

        @property {

            @setter("color.green".lref) // Set the property to arguments passed in setter
            Car color(Color color) @safe nothrow {
            	this.color_ = color;

            	return this;
            }

            // ...

            @setter(lref!Engine) // Set the property to arguments passed in setter
            Car engine(Engine engine) @safe nothrow {
            	this.engine_ = engine;

            	return this;
            }

            // ...

            @autowired // Automatically wire property using components from container identified by their types
            Car frontLeft(Tire frontLeft) @safe nothrow {
            	this.frontLeft_ = frontLeft;

            	return this;
            }

            // ...

            @callback(
                function (Locator!() locator, ref Car configured) {
                    configured.frontRight = locator.locate!Tire;
                }
            ) // Use a callback to configure the property, or entire object. Can be attached anywhere on component
            Car frontRight(Tire frontRight) @safe nothrow {
            	this.frontRight_ = frontRight;

            	return this;
            }

            // ...

            @autowired // Automatically wire property using components from container identified by their types
            Car backLeft(Tire backLeft) @safe nothrow {
            	this.backLeft_ = backLeft;

            	return this;
            }

            // ...

            @autowired // Automatically wire property using components from container identified by their types
            Car backRight(Tire backRight) @safe nothrow {
            	this.backRight_ = backRight;

            	return this;
            }

            // ...

        }

        // ...
    }
}

As seen above describing a component using annotation consists of annotating it with @component, optionally specyfing an identity using @qualifier, and annotating it with construction and configuration annotations such as:

  • @constructor (only on constructors) - annotates component with it's construction dependencies
  • @setter (only on setters) - annotates component with it's setter based dependencies
  • @autowired (not on constructors) - annotates component with it's setter based dependencies automatically
  • @autowired (on constructors) - annotates component with it's construction dependencies automatically
  • @callback (not on constructors) - annotates component with a custom function used to configure component
  • @callback (on constructors) - annotates component with a custom function used to create component
  • @contained (on component) - annotates component with information about container that manages it in a composite/joint container

Though annotations provide information about a component for framework, it does not automatically register them into container. To add annotated components to a container use scan family of functions. Example below shows how it is possible to register an entire module using just one line of code:

container.scan!(app); // load all annotated components from module "app"

Other forms of scan exists. Check api documentation to see alternatives of module based component registration if needed.

The result of running example, will yield into following output:

Uuh, what a nice car, Electric car with following specs:
Size:   Size(200, 150, 500)
Color:  Color(0, 255, 0)
Engine: app.ElectricEngine
Tire front left:        Tire(17 inch, 3 atm, divine tire)        located at memory 7FCC35376100
Tire front right:       Tire(17 inch, 3 atm, divine tire)        located at memory 7FCC35376140
Tire back left:         Tire(17 inch, 3 atm, divine tire)        located at memory 7FCC35376180
Tire back right:        Tire(17 inch, 3 atm, divine tire)        located at memory 7FCC353761C0

Check example below. Modify it, run it to understand annotation based configuration.

Functions

NameDescription
drive(car, name)
main()

Interfaces

NameDescription
Engine Interface for engines.

Classes

NameDescription
Car A class representing a car.
CarManufacturer A manufacturer of cars.
DieselEngine A concrete implementation of Engine that uses diesel for propelling.
ElectricEngine A concrete implementation of Engine that uses electricity for propelling.
GasolineEngine A concrete implementation of Engine that uses gasoline for propelling.
ManufacturedCar
Tire Tire, what it can represent else?

Structs

NameDescription
Color A struct that should be managed by container.
Size Size of a car.