Dependency injection

Dependency injection in ModularBr: how it works and its benefits.

Dependency injection is a common design pattern in software development that aims to reduce coupling between application components and facilitate code maintenance. In ModularBr, this pattern is widely used to manage dependencies between modules.

Dependency injection in ModularBr works as follows: modules define their dependencies, and the InjectorBr takes care of resolving the dependencies and instantiating the necessary objects at runtime. This allows modules to be developed independently, and the application can be easily modified without affecting other modules.

Furthermore, dependency injection in ModularBr brings other benefits, such as the ability to test modules in isolation and improving code readability and maintainability. With clear separation of responsibilities and dependencies between modules, it becomes easier to understand how the application works and make modifications or fixes without impacting other parts of the system.

In summary, dependency injection is a fundamental feature in ModularBr that contributes to code modularity, flexibility, and maintainability. It enables the creation of robust, scalable, and easy-to-maintain applications.

Instance registration

The strategy for constructing an instance with its dependencies involves registering all objects in a module and creating them on-demand or as a singleton instance. This 'registration' is called a Bind.

Creating a Bind to register instances:

  • Bind<>.Singleton: Creates an instance only once when the module is initialized.

  • Bind<>.SingletonLazy: Creates an instance only once when requested.

  • Bind<>.Factory: Creates an instance on demand.

  • Bind<>.SingletonInterface: Creates a singleton interface on demand using the .GetInterface method.

  • Bind<>.Instance: Adds an existing instance.

Example of usage:

unit nfe.module;

interface

uses
  dmfbr.module,
  nfe.repository, nfe.provider, nfe.controller;

type
  TNFeModule = class(TModule)
  public
    function Routes: TRoutes; override;
    function Binds: TBinds; override;
  end;

implementation

{ TNFeModule }

function TNFeModule.Binds: TBinds;
begin
  // Dependency Injection
  Result := [Bind<TRepositoryServer>.SingletonLazy,
             Bind<TControllerServer>.Singleton,
             Bind<TProviderORMBr>.Factory];
end;

function TNFeModule.Routes: TRoutes;
begin
  Result := [RouteModule('/nfe/v1/pdf/:key', TPDFModulo),
             RouteModule('/nfe/v1/xml/:key', TXMLModulo)];
end;

end.

WARNING

For a binding to be eligible for substitution, the binding MUST have the type declared in the Bind constructor. (e.g., Bind<TMyObjectType>()).

Constructor Parameters

Singleton<T> - SingletonLazy<T> - SingletonInterface<I> and Factory have the following parameters:

  1. Parameter AOnCreate

  2. Parameter AOnDestroy

  3. Parameter AOnConstructorParams

OnCreate

The AOnCreate parameter is an optional parameter of the methods. It is used to specify a procedure that should be executed when a new instance of the T class is created by the dependency injection container.

It is an anonymous procedure that can be passed as a parameter. The procedure should have an input parameter of type T, which is the newly created instance of the T class. AOnCreate is optional and can be used to execute any additional code that is necessary to configure the instance before it is used.

For example, if a class T has a property that needs to be configured with a specific value before it can be used, you can define a procedure that performs this configuration and then pass it as the AOnCreate parameter when creating a new instance of the T class.

InjectorBr.Factory<TMasterClass>(
  procedure (Value: TMasterClass)
  begin
    Value.IncludeChild(InjectorBr.Get<TChildClass>);
    Value.ValueInicial := true;
  end);

OnDestroy

The AOnDestroy parameter is an optional parameter of the methods. It is used to specify a procedure that should be executed when an instance of the T class is destroyed by the dependency injection container.

It is an anonymous procedure that can be passed as a parameter. The procedure should have an input parameter of type T, which is the instance being destroyed. AOnDestroy is optional and can be used to execute any additional code that is necessary before the instance is destroyed.

For example, if a class T has an external resource that needs to be released before the instance is destroyed, you can define a procedure that releases the resource and then pass it as the AOnDestroy parameter when creating a new instance of the T class.

InjectorBr.Factory<TMasterClass>(
  procedure (Value: TMasterClass)
  begin
    ShowMessage('Destroy: ' + Value.ClassName);
  end);

OnConstructorParams

The AOnConstructorParams parameter is an optional parameter of the methods. It is used to allow you to inject parameters into a constructor of a generic class when the instance is created by the dependency injection container.

The type of AOnConstructorParams is TConstructorCallback, which is a function type that you must define and pass as a parameter. The TConstructorCallback function returns an array of TValues. This array represents the parameters that will be injected into the constructor of the generic class.

By using AOnConstructorParams, you can customize the creation of an instance of a generic class to meet your specific needs. For example, if a generic class has a constructor that accepts parameters, you can use AOnConstructorParams to pass those parameters when creating an instance of the generic class. This can be especially useful if you need to inject additional dependencies into a generic class that cannot be resolved by the dependency injection container alone.

InjectorBr.Factory<TMasterClass>(nil, nil,
  function: TConstructorParams
  begin
    Result := [TValue.From<TChildClass>(InjectorBr.Get<TChildClass>)];
  end);

IMPORTANT Why is OnConstructorParams optional? In ModularBr, the Injector is responsible for handling parameter injection in class constructors. It has the ability to fetch the necessary dependencies and automatically inject them at the time of class instance creation.

When you define a class with a constructor that has parameters, the ModularBr Injector analyzes those parameters and determines the required dependencies. It then searches for these dependencies within the ModularBr context and injects them into the constructor of the class.

This means that you don't have to manually create dependency instances and pass them to the constructor. The Injector handles all of this automatically, making the dependency injection process more convenient and efficient. This approach of the Injector in ModularBr simplifies development, as you can focus on the class logic without worrying about dependency creation and management. ModularBr takes care of that for you, ensuring that dependencies are resolved and injected correctly.

Last updated