Extensibility

Motivation

A product is a living entity meaning it constantly changes and such does the code which comprises it. The developers would like to introduce changes to the existing codebase without modifying the existing elements to minimize the change for regression. This means that the original code should be designed in an extensible way to preserve the Open-Close principle.

Middleware

You may be familiar with the concept of middleware from web development. It is used there primarily to describe some functionality which is applied onto the incoming HTTP request during the pipeline processing. This concept is used here while expanding it to any type and essentially creating a fluent extension:

    public class ContainerRegistrationMiddleware<TIocContainer, TContainerConstraint> :
        RegistrationMiddlewareBase,
        IMiddleware<TIocContainer>
        where TIocContainer : class, TContainerConstraint
    {       
        public ContainerRegistrationMiddleware(IEnumerable<ICompositionModule> modules) : base(modules)
        {
        }

        public TIocContainer Apply(TIocContainer @object)
        {
            if (Modules != null)
            {
                var matchingModules = Modules.OfType<ICompositionModule<TContainerConstraint>>();
                var modules =
                    matchingModules.SortTopologically();                
                foreach (var compositionModule in modules)
                {
                    compositionModule.RegisterModule(@object);
                }
            }

            return @object;
        }
    }

As you can see the same type of the object is returned. Interestingly enough this kind of approach allows writing pure code if needed to.

Aspects

The main idea behind extensible architecture is to design software which is composed of small building blocks that are loosely coupled and can be replaceable. Sometimes these building blocks encompass an aspect of development or in our case the bootstrapping process. The framework allows extending any extensible type with any aspect which is done via the correspondent interface:

 public interface IHaveAspects<T>
    {       
        T UseAspect(IAspect aspect);
    }

An aspect could be anything: for instance extensibility itself is an aspect, too!

    public class ExtensibilityAspect : IAspect, IExtensible where T : class 
    { 
        private readonly T _extensible;
        
        public ExtensibilityAspect(T extensible)
        {
        _extensible = extensible;
        _middlewaresWrapper = new MiddlewaresWrapper<T>(_extensible);
        }

        private readonly MiddlewaresWrapper<T> _middlewaresWrapper;


        public T Use(IMiddleware<T> middleware)
        {
            _middlewaresWrapper.Use(middleware);
            return _extensible;
        }

        public void Initialize()
        {
            MiddlewareApplier.ApplyMiddlewares(_extensible, _middlewaresWrapper.Middlewares);
        }

        public string Id => $"Extensibility.{typeof(T).FullName}";

        public string[] Dependencies => new[] { "Modularity", "Discovery", "Platform" };
}

An aspect should provide an id for further identification and list of its own dependencies. The latter is necessary to avoid cyclic dependencies and ensure proper initialization flow.

Last updated