Bootstraps

Monday, June 1, 2015

Improve your startup sequences with decoupled convention based modules: Bootstraps

Intro

I love extensibility. If it comes in automatic-based-on-conventions form, even better. I love plugins, reflection and everything else that would allow my application to grow and change without touching existing code. I know…it’s hard, but I like it too much. So I’ll keep trying…and failing.

A bootstrap is a class that is instantiated only once. It gets executed at some point during the initialization sequence and performs some simple initialization from its constructor.

public class InitializeSomethingBootstrap {
    public InitializeSomethingBootstrap(ISomeDependency s) {
        s.Initialize();
    }
}

It might not look like much, but bear with me a little longer, I will show you some juice.

Real life use case

Let’s take AutoMapper for instance. Before using it, it needs to be configured.

    Mapper.CreateMap<Source, Destination>();

This line could be part of our application startup sequence, but as the domain evolves more of these lines will be necessary. Instead, we can move this kind of code to a bootstrap, we can have one of these bootstraps per each sub domain. Anytime a new subdomain joins the picture there would be absolutely no need to modify exiting code.

public class PersonAutoMapperBootstrap {
    public PersonAutoMapperBootstrap() {
        Mapper.CreateMap<Person, PersonDto>();
        Mapper.CreateMap<Person, PersonDetailedDto>();
    }
}

Now…There something about AutoMapper. I really don’t like static things, only very small stateless functions. But since AutoMapper is the best of its kind and there is no simple way around its statically-ness, I’d rather abstract it and hide its use away.

public class PersonAutoMapperBootstrap {
    public PersonAutoMapperBootstrap(IMappings mappings) {
        mappings
            .Add(Mapping.From<Person>().To<PersonDto>())
            .Add(Mapping.From<Person>().To<PersonDetailedDto>());
    }
}

Cool, right? The fancy fluent interface is a plus…in real life I might not have the time for it, but now…let’s fly. Behind this IMapping there could be a very simple implementation that just call the static method. Off course, there could be much more complex mappings, but this ain’t about abstracting AutoMapper, so let’s take that subject some other time.

Testability

Another huge advantage of abstractions is testing. We can provide a fake IMappings and seal this proper behavior with blood over stone (or even better: with unit tests).

public class PersonAutoMapperBootstrapFixture {
    [Theory, AutoMoqData]
    public void It_creates_a_mapping_from_Person_to_PersonDto(
        [Frozen] Mock<IMappings> mappings,
        PersonAutoMapperBootstrap sut) {

        mappings.Verify(m => 
            m.Add(
                It.Is<IMapping>(x => 
                    x.SourceType == typeof(Person)
                    && x.DestinationType == typeof(PersonDto)
                )
            )
        );  
    }
}

This code says that after creating an instance of PersonAutoMapperBootstrap, its dependency, IMappings which in this case is a Mock<IMappings> will be called with an IMapping with specified SourceType and DestinationType. Here I’m using: xUnit, Moq and AutoFixture. You will find a lot about testing with these tools on Ploeh’s.

Initialization sequence

I use behavior chains for my initialization sequences. With time I might need more steps, remove old…or what ever. This pattern has been very useful for me. For the bootstraps I need 2 steps.

public class FindBootstraps {
    public class Input {
        public IEnumerable<Assembly> Assemblies { get; set; }
    }
    public class Output {
        public IEnumerable<Type> Bootstraps { get; set; }
    }

    public static Output Run(Input input) {
        return new Output {
            Bootstraps = Assemblies
                .SelectMany(a => a.GetTypes())
                .Where(t => t.Name.EndsWith("Bootstrap"));
        }
    }
}

This one finds bootstrap types. Types contained inside one of the Assemblies and following the naming convention EndsWith("Bootstrap") .

public class ExecuteBootstraps {
    public class Input {
        public IResolver Resolver { get; set; }
        public IEnumerable<Type> Bootstraps { get; set; }
    }
    public class Output {}

    public static Output Run(Input input) {
        var resolver = input.Resolver;
        foreach(var b in input.Bootstraps)
            resolver.Resolve(b);
    }
}

This one executes the bootstraps (surprise!!). The find step must have been executed and the Dependency Injection must have been completely configured when we get here.

Another real life use case

Registries are great! But they are global resources by definition and people tend to implement them as a static resource. I don’t like statics, remember?

This is a perfect job for the bootstrap:

public class PeopleErrorHandlerBootstrap {
    public PeopleErrorHandlerBootstrap(
        IErrorHandlerRegistry errorHandlers,
        IPeopleErrorHandler peopleErrorHandler) {

        errorHandlers.Add(peopleErrorHandler);
    }   
}

As the application grows more and more error handlers will join in. We can keep up just creating more and more bootstraps. There won’t be a need to modify existing code and yet we’ll be extending the application.

Execution order

Sometimes, it will make sense to execute a bootstrap after another(s). If the DAG of prerequisites gets really messy, we should create some convention to make sure they are executed in proper order, e.g: A PriorityAttribute. For the simple case, specifying the prerequisites as dependencies is enough.

public class SomeBootstrap {
    public SomeBootstrap(SomeOtherBootstrapThatMustBeExecuteBeforeThisOne b) {
        b.DoSomethingUseful();
    }
}

Outro

Bootstraps simplify extensibility in a decoupled testable manner. They are a simple technique that solves a non very small problem. They could be seamlessly used along with many technologies with no sweat. We might just require some automatic discover-ability (i.e: Reflection). They are in use in many commercial-real-life projects I have been involved.