Skip to content

Dependency Injection

Marc Wittke edited this page Sep 29, 2018 · 1 revision

Backend.Fx supports (however, does not require) the use of a dependency injection container. It is agnostic of the concrete implementation and tries to hide everything behind abstractions named by the underlying pattern, not the concrete classes used by some injection containers.

IBackendFxApplication

This interface should be implemented by a singleton and holds a reference to the dependency injection composition root. This instance knows how to "wire" all services of the business layer. It is responsable of opening and closing scopes for logical operations e.g. a web request using the scope manager. The more special BackendFxDbApplication is also responsable for creating/maintaining the application database, if applicable.

public interface IBackendFxApplication : IDisposable
{
    /// <summary>
    /// You should use the <see cref="IScopeManager"/> to open an injection scope for every logical operation.
    /// In case of web applications, this refers to a single HTTP request, for example.
    /// </summary>
    IScopeManager ScopeManager { get; }

    /// <summary>
    /// The composition root of the dependency injection framework
    /// </summary>
    ICompositionRoot CompositionRoot { get; }

    /// <summary>
    /// allows asynchronously awaiting application startup
    /// </summary>
    ManualResetEventSlim IsBooted { get; }

    /// <summary>
    /// Initializes ans starts the application (async)
    /// </summary>
    /// <returns></returns>
    Task Boot();
}

ICompositionRoot

Encapsulates the injection framework of choice. The implementation follows the Register/Resolve/Release pattern. Usage of this interface is only allowed for framework integration (or tests). NEVER (!) access the injector from the domain or application logic, this would result in the Service Locator anti pattern.

public interface ICompositionRoot : IDisposable, IDomainEventHandlerProvider
{
    /// <summary>
    /// Gets a service instance by providing its type
    /// </summary>
    /// <param name="serviceType"></param>
    /// <returns></returns>
    object GetInstance(Type serviceType);

    /// <summary>
    /// Gets a service instance by providing its type via generic type parameter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    T GetInstance<T>() where T : class;

    IEnumerable<T> GetInstances<T>() where T : class;

    void Verify();

    void RegisterModules(params IModule[] modules);
}

IModule

A logical set of related services, that should be registered together. There is a strong tendency to have a module for each assembly, but this is not a hard rule at all.

public interface IModule
{
    void Register(ICompositionRoot compositionRoot);
}

A typical domain module using SimpleInjector looks like this:

public class AppDomainModule : DomainModule
{
    private readonly ITenantManager tenantManager;
    private readonly IExceptionLogger exceptionLogger;

    public AppDomainModule(ITenantManager tenantManager, IExceptionLogger exceptionLogger)
            : base(typeof(ConfigurationSetting).Assembly, typeof(AnyDomainEntity).Assembly)
    {
        this.tenantManager = tenantManager;
        this.exceptionLogger = exceptionLogger;
    }

    protected override void Register(Container container, ScopedLifestyle scopedLifestyle)
    {
        base.Register(container, scopedLifestyle);
        container.RegisterInstance(exceptionLogger);
        container.RegisterInstance(tenantManager);
        container.RegisterConditional<IClock, FrozenClock>(c => !c.Handled);

        container.RegisterInstance(new CultureInfo("en-US"));

        container.RegisterSingleton<ITemplateEngine, HandlebarsDotNetTemplateEngine>();
        container.RegisterSingleton<IDefaultMessageTemplateProvider, DefaultMessageTemplateProvider>();
        container.RegisterSingleton<ISettingSerializerFactory, AppSettingSerializerFactory>();
    }
}

IScopeManager

Responsable to begin a new injection scope for the given identity in the given tenant. The identity may be used for authorization purposes inside the domain layer. The caller is responsible to complete the scope after finalization. Usage of GetCurrentScope() is to be considered evil outside testing code, since it smells like service locator.

public interface IScopeManager
{
    IScope BeginScope(IIdentity identity, TenantId tenantId);

    IScope GetCurrentScope();
}

IScope

To resolve scoped services. Should only be used in framework code, not in business application code.

public interface IScope : IDisposable
{
    TService GetInstance<TService>() where TService : class;
    object GetInstance(Type serviceType);

    IEnumerable<TService> GetAllInstances<TService>() where TService : class;
    IEnumerable GetAllInstances(Type serviceType);
}
Clone this wiki locally