Startup sequence in Chains

Tuesday, May 12, 2015

Modularize your program’s startup sequence with behavior chains

Intro

Before you launch a rocket into space you must warm up your engines, do many other weird things and count down from 10. Something like 10…9…8…and so on. Any application must do kind of the same. There are several steps that must be executed before the application is ready to do its work.

public class Program {
    public static void Main(string[] args) {
        ConfigureIntegrationWithThirdPartyLibs();
        LoadPlugins();
        ConfigureDependencyInjection();
        WarmUpCache();
    }
    //...
}

I’ve been gentle with myself and hid the actual dirty tasks behind some static methods. Some of these methods might grow a lot with time. Some might die, others might be born. This code will probably change a lot. There will be a lot of common tasks, for instance third party libraries, Dependency Injection and plugins might require some reflection scanning of a set of assemblies. These common tasks will result in weird refactorings or code repetition. In any case this code’s maintainability will wear off and since this part does not contain any domain logic it will not be under great favor from developers nor management.

Deep in it

Gentleness is over. We need to see what’s inside. Most of this code is hypothetical. The plugin system is trivial: all libraries in the plugin folder will be loaded and from that point onward they will be part of AllAssemblies, so when the Dependency Injection (AutoFac in this case) kicks in the plugins will be registered.

private static IEnumerable<Assembly> AllAssemblies {
    get {
        return AppDomain.Current
            .GetAssemblies();
    }
}

public static void ConfigureIntegrationWithThirdPartyLibs() {
    var assemblies = AllAssemblies;
    ConfigureLibrary1(assemblies);
    // ...
    ConfigureLibraryN(assemblies);
}

public static void ConfigurePlugins() {
    foreach(var plugin in Directory.GetFiles("plugins", "*.dll")) {
        Assembly.LoadFrom(plugin);
    }
}

public static void ConfigureDependencyInjection() {
    var builder = new ContainerBuilder();

    foreach(var assembly in AllAssemblies) {
        builder.RegisterAssemblyTypes(assembly )
           .AsImplementedInterfaces();
    }

    var container = builder.Build();
    Resolver = new Resolver(container);
}

public static void WarmUpCache() {
    var types = AllAssemblies.SelectMany(a => a.GetTypes);
    var cacheTypes = types.Where(t => t.Implements<ICache>();
    var caches = cacheTypes.Select(t => (ICache)Resolver.Resolve(t));

    foreach(var cache in caches) {
        cache.WarmUp();
    } 
}

These initialization steps are well defined, have clear boundaries; are independent, they don’t really know each other; are sequential and all of them are always executed. This is text book behavior chains.

Links

I will convert each step into a class, some steps I will break into very very small ones. Then, will create inner classes Input and Output for the Run() method, the one that will do the actual work.

public class FindAssemblies {
    public class Input {
        public string ApplicationRoot { get; set; }
    }

    public class Output {
        public IEnumerable<Assembly> Assemblies { get; set; }
    }

    public static Output Run(Input input) {
        var applicationRoot = input.ApplicationRoot;
        var main = LoadAssemblies(applicationRoot);

        var pluginsFolder = Path.Combine(applicationRoot, "plugins");
        var plugins = LoadAssemblies(pluginsFolder);

        var assemblies = main.Concat(plugins)
                .ToList();

        return new Output {
            Assemblies = assemblies
        }
    }

    private static IEnumerable<Assembly> LoadAssemblies(string path) {
        return Directory.GetFiles(path, "*.dll")
            .Select(file => Assembly.LoadFrom(file));
    }
}

This isn’t one of the very small steps. It could have been broken in 2 but it might be too aggressive to start with.

Next one is part of the same logic.

public class LoadTypes {
    public class Input {
        public IEnumerable<Assembly> Assemblies { get; set; }
    }

    public class Output {
        public IEnumerable<Type> Types { get; set; }
    }

    public static Output Run(Input input) {
        var types = input.Assemblies
            .SelectMany(a => a.GetTypes())
            .ToList();

        return new Output {
            Types = types
        }
    }
}

This step was not there before. It looks so trivial you might feel tempted to remove it. I can tell you: it’s very useful as there are usually many steps that need to scan all the assemblies for some types fulfilling some criteria. This will save us some repeated code.

I itch just by saying it: repeated code…

Next Dependency Injection.

public class ConfigureDependencyInjection {
    public class Input {
        public IEnumerable<Assembly> Assemblies { get; set; }
    }

    public class Output {
        public IResolver Resolver { get; set; }
    }

    public static Output Run(Input input) {
        var builder = new ContainerBuilder();

        foreach(var assembly in input.Assemblies) {
            builder.RegisterAssemblyTypes(assembly )
               .AsImplementedInterfaces();
        }

        var container = builder.Build();
        var resolver = new AutoFacResolver(container);

        return new Output {
            Resolver = resolver
        }
    }
}

So far this just looks like making it longer, right? Yes, but it’s also much more flexible as each step has been transformed into a pure function. The steps can be reused, reorder, and many other REs. I know…that won’t get me a sale. I have more…

public class FindCaches {
    public class Input {
        public IEnumerable<Type> Types { get; set; }
    }

    public class Output {
        public IEnumerable<ICache> Caches { get; set; }
    }

    public static Output Run(Input input) {
        var types = AllAssemblies.SelectMany(a => a.GetTypes);
        var cacheTypes = types.Where(t => t.Implements<ICache>());
        var caches = cacheTypes.Select(t => (ICache)Resolver.Resolve(t))
            .ToList();

        return new Output {
            Caches = caches
        }
    }
}

public class WarmUpCaches {
    public class Input {
        public IEnumerable<ICache> Caches { get; set; }
    }

    public class Output {
    }

    public static Output Run(Input input) {
        foreach(var cache in input.Caches) {
            cache.WarmUp();
        }

        return new Output();
    }
}

Here I have split the finding of caches from it’s implementation. We have now two kinds of unrelated steps we can improve (or break) on their own. By splitting we decouple, decoupling brings lot of good things.

Testing

Before we create more steps let’s see this huge advantage:

public class WarmUpCachesFixture {
    [Test]
    public void It_calls_WarmUp_in_caches() {
        var cache = Mock.Of<ICache>();
        var input = new WarmUpCaches.Input {
            caches = new[] { cache }
        };

        WarmUpCaches.Run(input);

        Mock.Get(cache)
            .Verify(m => m.WarmUp());
    }
}

We have taken a single step and 100% test covered it. This means, it doesn’t really matter what the other steps do, this one will do its job…for ever and ever until the end of time. Provided a collection of caches, this guy will call WarmUp() in all of them.

We just need to create tests to ensure the other steps do their work.

Third parties

Integrating with third party libraries is one of the things that make our code go bad. Each with their own concepts that force us to do things their way, which is not always right.

public class ConfigureLibrary1 {
    public class Input {
        public IEnumerable<Assembly> Assemblies { get; set; }
        public IEnumerable<Type> Types { get; set; }
    }

    public class Output {
    }

    public static Output Run(Input input) {
        // Configure it

        return new Output();
    }
}

Here I ran off imagination. The thing is any example that would pop into my mind requires some not very pretty code. So let’s just say I can convert each of the ConfigureLibraryX() into a step.

Gluing it together

At some point I have to create the chain with all the steps I have created. I will create a static class just for it. It will create the initialization function in its class initializer and will have only one method: Run().

public static class StartUpSequence {
    private class Input {
        public string ApplicationRoot { get; set; }
    }
    private class Output {}

    static StartUpSequence() {
        Sequence = BehaviorWiring.Chain<Input, Output>(
            Step<FindAssemblies>(),
            Step<LoadTypes>(),
            Step<ConfigureLibrary1>(),
            Step<ConfigureLibrary2>(),
            //...
            Step<ConfigureLibraryN>(),
            Step<ConfigureDependencyInjection>(),
            Step<FindCaches>(),
            Step<WarmUpCaches>()
        );
    }

    public static void Run() {
        var currentDirectory = Directory.GetCurrentDirectory();

        Sequence(new Input {
            ApplicationRoot = currentDirectory
        });
    }

    public static Delegate Step<TStep>() {
        return BehaviorWiring.Extract(typeof(TStep), "Run");
    }
}

Using this is gonna be pretty cool.

public class Program {
    public static void Main(string[] args) {
        StartUpSequence.Run();
    }
}

I can almost hear it: 10..9…8… Don’t you?

Eppur si muove

New step? Create its class.

public class NewStep {
    public class Input {}
    public class Output {}
    public Output Run(Input input) { /* ... */ }
}

Test it.

public class NewStepFixture {
    [Test]
    public void It_does_what_is_suposed_to_do() {
        NewStep.Run(new NewStep.Input());

        // Assert It does what is suposed to do
    }
}

Add it to the step list in StartUpSequence

        Sequence = BehaviorWiring.Chain<Input, Output>(
            // Other steps hidden
            Step<WarmUpCaches>(),
            Step<NewStep>()
        );

Some step no longer needed: remove it from the step list and remove its test and class. Some step needs another input, modify its Input, make sure some of the previous steps would provide, remember to update your tests. You could even create a convention based technique to automatically discover the steps.

Outro

We all have initialization sequences. The best ones I’ve seen are very sophisticated hierarchies. This is just another way to go, more kind of functional. Yes you’d need to type more. Don’t know about you, I don’t mind. With this technique your steps will be modules, with all the advantages that come with modularity. It’s easy, very, to modify the sequence. I definitely see some ROI that could be sold to the management.

No comments :

Post a Comment