Skip to content
Howard van Rooijen By Howard van Rooijen Co-Founder
Work Smarter with Convention Over Configuration and the Endjin Composition Framework

Large productivity gains can be made by adopting a "Convention over Configuration" (CoC) approach when designing new solution architectures.

To harness the full power of the Convention over Configuration approach, the development team must take a holistic approach by examining the ceremony required at every stage within the Software Development Life Cycle.

In a previous post I've talked about the mantra of "Work Smarter, Not Harder":

  • Do
  • Recognise Repetition
  • Codify

By articulating and formalising patterns and approaches into conventions, you add an incredibly powerful tool to your arsenal – the ability to leverage assumptions.

In his last post Matthew announced Endjin.Composition a new framework, based on MEF and Castle Windsor, for conventionalising component discovery and registration. The genesis of the idea came from a previous project, in the last year I've tried tweaking and rewriting the code to eliminate the need for a marker interface for registration, as I wasn't happy with the friction the consumer of the code would experience to implement. In the original code each project had to implement a class which which was used for type discovery and registration:

public class InfrastructureRegistrarMarker : IComponentRegistrarMarker {}

and type discovery and registration was carried out in the following way, utilising the marker:

[Export(typeof(IComponentRegistrar))]
public class InfrastructureComonentRegistrar : IComponentRegistrar
{
   public void Register(IWindsorContainer container)
   {
      container.Register(
       AllTypes.Pick()
        .FromAssembly(Assembly.GetAssembly(typeof(InfrastructureRegistrarMarker)))
        .WithService.FirstNonGenericCoreInterface("MefAndWindsor.Domain"));
   }
}

Not exactly a huge amount of code – but if you create lots of projects, or if you want this behaviour to be part of an Architecture Framework like Sharp Architecture, on which many people build their own solutions – it's a little too heavyweight and there is too much duplication.

With Endjin.Composition we refactored the "Registrar" code to be a little more extensible and also to take advantage of Castle Winsor's innate component installation mechanism:

public abstract class NamespaceInstallerBase : IWindsorInstaller
{
    private readonly IEnumerable<Type> markerTypes;
    private readonly string partialNamespace;

    protected NamespaceInstallerBase(IEnumerable<Type> markerTypes, string partialNamespace)
    {
        if (markerTypes == null)
        {
            throw new ArgumentNullException("markerTypes");
        }

        if (partialNamespace == null)
        {
            throw new ArgumentNullException("partialNamespace");
        }

        this.markerTypes = markerTypes;
        this.partialNamespace = partialNamespace;
    }

    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        foreach (var type in this.markerTypes)
        {
            container.Register(
                    AllTypes.FromAssembly(type.Assembly)
                            .Pick()
                            .If(f => f.Namespace.Contains(this.partialNamespace))
                            .WithService
                            .DefaultInterface());

        }
    }
}
Discover your Power BI Maturity Score by taking our FREE 5 minute quiz.

Next, we provide a generic version:

public abstract class NamespaceInstallerBase<T> : NamespaceInstallerBase
{
    protected NamespaceInstallerBase(string partialNamespace)
                            : base(new[] { typeof(T) }, partialNamespace)
    {
    }
}

One of the main purposes of building Who Can Help Me was to demonstrate building a convention based architecture. We wanted to make abstract concepts like Domain, Framework, Infrastructure, Tasks and Presentation concrete architectural constructs. This worked well and has now been adopted for Sharp Architecture V2.0. Now we have actually created these constructs we can now conventionalise them.

So we've achieved the Do and Recognise Repetition part of Work Smarter mantra, next we need to Codify.

If we have concepts such as Domain, Framework, Infrastructure and Tasks, we need to turn them into conventions within our solution – we must ensure that we design our Solution and Project structure to capture these concepts, enshrining them in our namespaces.

Programming C# 10 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

If we revisit the code from "A Step by Step Guide to MongoDB for .NET Developers" the original project structure looked like:

    +---Contracts <—this is where interfaces are kept
    |   +---Framework
    |   |   \---Infrastructure
    |   |       +---Repositories
    |   |       \---Specifications
    |   +---Infrastructure
    |   |   \---Repositories
    |   \---Tasks
    +---Domain
    +---Framework <— this is where contract implementations are kept
    |   +---Extensions
    |   +---Infrastructure
    |   |   +---Norm
    |   |   \---Repositories
    |   \---Specifications
    +---Infrastructure <— this is where contract implementations are kept
    |   +---DataSources
    |   \---Specifications
    +---Properties
    \---Tasks <— this is where contract implementations are kept

In the above, we have started to solidify our concepts (Domain, Framework, Infrastructure, Tasks) into a project structure, we have separated out all of our contracts (interfaces) from their implementations. But there are a few anomalies – such as there being an Infrastructure folder under Framework.

In order to make use of Endjin.Composition we will make two changes, first we'll create a Configuration folder with an "Installers" sub-folder and we will rebase the Framework\Infrastructure folder to Infrastructure (and apply ReSharper's "Adjust Namespaces" refactoring to ensure that namespaces match folder structure):

    +---Configuration 
    |   \---Installers
    +---Contracts
    |   +---Framework
    |   |   \---Infrastructure
    |   |       \---Specifications
    |   +---Infrastructure
    |   |   \---Repositories
    |   \---Tasks
    +---Domain
    +---Framework
    |   +---Extensions
    |   \---Specifications
    +---Infrastructure
    |   +---DataSources
    |   +---Norm
    |   +---Repositories
    |   \---Specifications
    +---Properties
    \---Tasks

Because we have now conventionalised our project structure, we can start the last step of the Work Smarter mantra: Automate, which we are able to do by leveraging assumptions.

Out of the box Endjin.Composition comes with a class that contains these conventions:

public static class ArchitecturalLayers
{
    public static string Configuration
    {
        get { return "Configuration"; }
    }

    public static string Domain
    {
        get { return "Domain"; }
    }

    public static string Framework
    {
        get { return "Framework"; }
    }

    public static string Infrastructure
    {
        get { return "Infrastructure"; }
    }

    public static string Presentation
    {
        get { return "Presentation"; }
    }

    public static string Tasks
    {
        get { return "Tasks"; }
    }
}

Which allows us to Codify the convention, the Installer below will auto-register all types within the "Infrastructure" namespace:

public class InfrastructureInstallerBase <T> : NamespaceInstallerBase <T>
{
    public InfrastructureInstallerBase() : base(ArchitecturalLayers.Infrastructure)
    {
    }
}

Now we are ready to do the final step in the Work Smarter mantra: Automate. To implement an installer in our MongoDB project (we create a new class called "InfrastructureInstaller" which inherits from InfrastructureInstallerBase passing a generic parameter of type InfrastructureInstaller. This self referencing trick allows us to not have to have some type of marker interface that is used as a way to identify the current assembly. We also decorate the installer class with a MEF export attribute so that the type can be auto-discovered and executed at runtime:

[Export(typeof(IWindsorInstaller))]
public class InfrastructureInstaller : InfrastructureInstallerBase < InfrastructureInstaller >
{
}

To update the code in "A Step by Step Guide to MongoDB for .NET Developers" to take advantage ofEndjin.Composition we also need to provide a Framework and Tasks Installer:

[Export(typeof(IWindsorInstaller))]
public class FrameworkInstaller : FrameworkInstallerBase < FrameworkInstaller >
{
}

and

[Export(typeof(IWindsorInstaller))]
public class TasksInstaller : TasksInstallerBase < TasksInstaller >
{
}

Hardly any code required at all! Consumers no longer need to know about the inner workings of Windsor or MEF.

Next we need to refactor the program class – to allow us to initialise Endjin.Composition, the original code has been moved into DoWork():

private static void Main(string[] args)
{
    Initialize();
    DoWork();
}

private static void Initialize()
{
    ApplicationServiceLocator.Initialize(
        new WindsorServiceContainer(),
        new MefWindsorBootstrapper(
          new AssemblyCatalog(typeof(Program).Assembly)));
}

To Initialize we simply call ApplicationServiceLocator.Initialize and pass in a new WindsorServiceContainer and a new MefWindsorBootStrapper into which we pass in a new AssemblyCatalog of the current assembly (which container the installer we want to be discovered).

The final change we need to make is to call the ServiceLocator to resolve a concrete implementation of IClubTasks rather that creating one explicitly:

var clubTasks = ApplicationServiceLocator.Container.Resolve<IClubTasks>();

Calling a ServiceLocator within your code is generally held as an anti-pattern, but in this case we need to create a root composition container – the object which will resolve and inject all other dependencies – and this is the simplest way.

To recap, using Endjin.Composition allows you to simply discover, register and compose your application based on simple, yet powerful conventions. Hopefully you will see Endjin.Composition as one of the fundamental constructs within Sharp Architecture V2.0. The code is available on the endjin GitHub Samples repo under the "Endjin-Composition" branch.

Work smarter, not harder.

@HowardvRooijen | @endjin

Howard van Rooijen

Co-Founder

Howard van Rooijen

Howard spent 10 years as a technology consultant helping some of the UK's best known organisations work smarter, before founding endjin in 2010. He's a Microsoft ScaleUp Mentor, and a Microsoft MVP for Azure and Developer Technologies, and helps small teams achieve big things using data, AI and Microsoft Azure.