Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IoC Initialize Service with heavy-work in constructor but avoiding a temporal Init() method

Hi I am using an IoC container and I would like to initialize a service (part of which involves 'heavy work' talking to a database) within the constructor.

This particular service stores information that is found by a injected IPluginToServiceProviderBridge service, this information is saved in the database via a UnitOfWork.

Once everything is boot-strapped, controllers with commands, and services with handlers, are used for all other interaction. All commands are wrapped within a lifetime scope so saving and disposing of the UnitOfWork is done by the handler and not the service (this is great for clean code).

The same neatness and separation of concerns for saving and transactions does not apply for the Initializer within the service as everything takes place in the constructor:

public PluginManagerService(
    IPluginToServiceProviderBridge serviceProvider,
    IUnitOfWork unitOfWork)
{     
    this.unitOfWork = unitOfWork;
    this.serviceProvider = serviceProvider;

    lock (threadLock)
    {
        if (initialised == false)
        {
            LinkPluginsWithDatabase();
            initialised = true;
        }

        // I don't like this next line, but 
        // not sure what else to do
        this.UnitOfWork.Save(); 
    }
}

protected void LinkPluginsWithDatabase()
{
    var plugins =
        this.serviceProvider.GetAllPlugins();

    foreach (var plugin in plugins)
    {
        var db = new PluginRecord
        {
            interfaceType = plugin.InterfaceType;
            var id = plugin.Id;
            var version = plugin.Version;
        }
        // store in db via unit of work repository
        this.unitOfWork.PluginsRepository.Add(db);
    }
}

A couple of points:

Ideally I want to avoid using a factory as it complicates handling of scope lifetimes, I would be happy to refactor for better separation if i knew how.

I really want to avoid having a separate Init() method for the service, whilst it would allow for transaction and saving via command/handler, lots of checking code would be required and I believe this would also introduce temporal issues.

Given the above, is it acceptable to call UnitOfWork.Save() within my constructor or could I refactor for cleaner code and better separation?

like image 915
morleyc Avatar asked Jun 22 '12 02:06

morleyc


1 Answers

Letting your service's constructor do more than just store its dependencies in private fields is considered bad practice when applying dependency injection, since this allows the construction of the object graph to fail, slows down building of the graph, and complicates unit testing.

What I read from your question is that you need to do some initialization when the application starts. That's fine, since it's quite normal to have some initialization phase, but don't do this inside a constructor. Just move this initialization to the end of your application start-up code, after you configured the container (and after you optionally verified your configuration).

I imagine your code to look like this:

public void Application_Start(object s, EventArgs e)
{
    Container container = new Container();

    Bootstrap(container);

    InitializeApplication(container);
}

private void InitializeApplication(
    Container container)
{
    using (this.container.BeginLifetimeScope())
    {
        var pluginManager = this.container
            .GetInstance<PluginManagerService>();

        pluginManager.LinkPluginsWithDatabase();

        var unitOfWork =
            container.GetInstance<IUnitOfWork>();

        unitOfWork.Save();
    }
}

You could even write a decorator for your PluginManagerService, but this might be a little bit over engineered, BUT... it might look like this:

public class InitializingPluginManagerServiceDecorator
    : IPluginManagerService
{
    private static readonly object syncRoot = new object();
    private static bool initialized;

    private IPluginManagerService decorated;
    private Container container;

    public InitializingPluginManagerServiceDecorator(
        IPluginManagerService decorated,
        Container container,
        IPluginToServiceProviderBridge serviceProvider)
    {
        this.pluginManagerService = pluginManagerService;
        this.container = container;
        this.serviceProvider = serviceProvider;
    }

    public void PluginManagerServiceMethod()
    {
        this.InitializeInLock();        

        this.decorated.PluginManagerServiceMethod();
    }

    private void InitializeInLock()
    {
        if (!initialized)
        {
            lock (syncRoot)
            {
                if (!initialized)
                {
                    this.InitializeInScope();
                }
            }

            initialized = true;    
        }
    }

    private void InitializeInScope()
    {
        using (this.container.BeginLifetimeScope())
        {
            this.InitializeWithSave();
        }
    }

    private void InitializeWithSave()
    {
        var uow =
            this.container.GetInstance<IUnitOfWork>()

        var initializer = this.container
            .GetInstance<PluginManagerServiceInitializer>();

        initializer.Initialize();

        uow.Save();    
    }
}

This decorator can be wrapped around an IPluginManagerService, and ensures that the system is initialized just before the IPluginManagerService us used for the first time, and ensures that it only initialized just once. The actual initialization logic is moved to a separate class (SRP), on which the decorator depends:

public class PluginManagerServiceInitializer
{
    private IUnitOfWork unitOfWork;
    private IPluginToServiceProviderBridge serviceProvider;

    public PluginManagerServiceInitializer(
        IUnitOfWork unitOfWork,
        IPluginToServiceProviderBridge serviceProvider)
    {
        this.unitOfWork = unitOfWork;
        this.serviceProvider = serviceProvider;
    }

    public void Initialize()
    {
        var plugins =
            from plugin in this.serviceProvider.GetAllPlugins()
            select new PluginRecord
            {
                interfaceType = plugin.InterfaceType;
                var id = plugin.Id;
                var version = plugin.Version;
            };

        unitOfWork.PluginsRepository.AddRange(plugins);
    }
}
like image 114
Steven Avatar answered Oct 23 '22 05:10

Steven