Modularity

Motivation

Modern-day apps are far from monolith state meaning they are composed of several components. These components are loosely-coupled and are often discovered during app startup. This approach allows the developers to substitute the modules easily and encapsulate the required functionality. This greatly aids preserving the Open-Close Principle of S.O.L.I.D. software development.

Plugin Model

There are all kinds of modules which can range from UI to simple configuration. One of the most frequent use cases for the need of introducing a composition module is to register the private dependencies of an assembly which should be otherwise unknown to the main app. This is actually a plugin case where system can consume any number of plugins by some abstraction without knowing the concrete implementation. (see the similarity to the case of dependency injection where the abstraction is used, too). The composition module would need to simply implement the abstraction:

using Solid.Practices.IoC;

class TestCompositionModule : ICompositionModule<IDependencyRegistrator>
{
    public void RegisterModule(IDependencyRegistrator dependencyRegistrator)
    {
        dependencyRegistrator.AddSingleton<IService, Service>();
    }
}

These modules are then composed together and executed during app startup thus making sure all related functionality is ready.

Dependencies

Sometimes modules could depend one on another. These dependencies should be handled by the appropriate class (see Bootstrapping for explanation). The information about the dependencies should be exposed via IHaveDependencies interface and the module itself should be identified using IIdentifiable interface. Alternatively, the DependenciesAttribute and IdAttribute can also be used. Let's see an example of usage:

using Solid.Core;
using Solid.Practices.IoC;
using Solid.Practices.Modularity;

class TestCompositionModule : ICompositionModule<IDependencyRegistrator>, IIdentifiable
{
    public void RegisterModule(IDependencyRegistrator dependencyRegistrator)
    {
        //register dependencies
    }

    public string Id => "TestModule";
}

class TestCompositionModule2 : ICompositionModule<IDependencyRegistrator>, IHaveDependencies
{
    public void RegisterModule(IDependencyRegistrator dependencyRegistrator)
    {
        //register dependencies
    }
    
    public string[] Dependencies => new [] {"TestModule"};
}

These two modules should be topologically sorted using these dependencies' constraints. This can be achieved using the correspondent extension method

var modules = new [] {new TestCompositionModule(), new TestCompositionModule2()};
var sortedModules = modules.SortTopologically();

That's it! Now the modules will be invoked in the correct order :)

Last updated