質問

The Situation

I recently refactored my main Big Blob domain type into a facade for it's various aspects. So far the facade and the individual aspect services are working like a charm but creating the whole thing and wiring everything together is done in a Big Blob Factory for the type with Poor Man's Dependency Injection. It's really annoying having to put the whole thing together by hand so I feel that I have traded a Big Blob Domain type for a Big Blog Domain type factory :-/.

I'm trying to follow the SOLID Principles and so far the connection of the domain with my view layer is really working pretty well thanks to the QueryHandler and CommandHandler abstractions and my favorite Dependency Injection Container (SimpleInjector to the rescue).

Now I wonder how I can make use of the SimpleInjector to do the work my larger than life factory is doing by hand.

Some Sample Code to Illustrate My Case

Note that the actual IUser is created by a separate factory elsewhere and being handed in as a property of a Command/Query parameter object, so in my book it is not to be treated as a service (e.g. handed in by the DI container) but rather as data.

Before the refactoring:

class BigDomainBlob : IDomainService
{
    public BigDomainBlob(Config config)
    {
        // Set up all the big blog business with the help of the
        // given configuration ...
    }

    // Lots of code interleaving the various aspects of the domain
}

interface IUser : System.Security.Principal.IPrincipal
{
    // Session information, ip address, etc.
}

class BigDomainBlobFactory : IFactory<IUser, IDomainService>
{
    // To this point the DI container can help me autowire the
    // factory's dependencies
    public BigDomainBlobFactory(/* ... */)
    {
        // Injecting persistence strategies and client specific
        // plugins to customize the service creation
    }

    public IDomainService Create(IUser user)
    {
        return new BigDomainBlob(new BigDomainBlob.Config
        {
            // Plenty of configuration code to tailor
            // the service to the needs of the given user
        });
    }
}

After the refactoring:

class NeatDomainFacade : IDomainService
{
    private readonly IDataHandlingService service1;
    private readonly IMetaDataService service2;

    public NeatDomainFacade(
        IDataHandlingService service1,
        IMetaDataService service2)
    {
        this.service1 = service1;
        this.service2 = service2;
    }

    // Forwarding the IDomainService functionality
    // to the different aspects of the domain
}

class NeatDomainFacadeFactory : IFactory<IUser, IDomainService>
{
    // To this point the DI container can still help me autowire the
    // factory's dependencies
    public NeatDomainFacadeFactory(/* ... */)
    {
        // Injecting persistence strategies and client specific
        // plugins to customize the service creation
    }

    // I still have to tailor every aspect of the domain to the
    // specific needs of a user by hand so the factory is going nuts
    public IDomainService Create(IUser user) 
    {
        // From here on I'm on my own hand wiring everything together ...

        // ...
        // Lots of code to set up user role, tailor SQL queries, etc.
        // ...

        // Handwiring my aspect dependencies and handing them in
        // to my fresh domain service
        var handWiredServiceForDataHandling = new Internal.DataService(
            someServiceDependency,
            anotherServiceDependency,
            new Internal.DataService.Config
            {
                SomeUserSpecificDbQueryString = "..."
            });

        var handWiredServiceThatAlsoUsesDataHandling = new Internal.MetaDataService(
            handWiredServiceForDataHandling,
            yetAnotherServiceDependency,
            new Internal.MetaDataService.Config
            {
                CustomizedMetaDataRetrievalString = "..."
            });

        // Even more code to hand wire and configure other dependencies ...

        // Finally handing in the service dependencies and creating the domain service
        return NeatDomainFacade(
            handWiredServiceForDataHandling, 
            handWiredServiceThatAlsoUsesDataHandling);
    }
}

The Question

So you see: all the different aspects not only need other services but also some data configuration to customize their behavior for the user so all of them have to be configured at a stage where the user information is available - meaning I can't configure them at startup time. I thought I got my head around the dependency injection way by now but I just don't see how I am to achieve this with the help of auto wiring.

I'm successfully injecting and using the factory to create the domain service in my QueryHandlers/CommandHandlers and like I said: the rest of the application architecture seems to work out pretty well - I hardly ever have to touch the composition root, just put the dependencies into the constructor and done. If only I could solve this pita domain service creation :-)

Help very much appreciated!

Edit:

I did the refactoring steven suggested in his answer but now I'm faced with a lifestyle issue. While hand wiring I created every dependency exactly once for every instance of the IDomainService and reused these for cross-dependencies. Some code:

public IDomainService Create(IUser user)
{
    // All dependencies are created exactly once and are
    // being reused throughout this method
    var serviceA = new ServiceA();
    var serviceB = new ServiceB(serviceA);
    var serviceC = new ServiceC(serviceA, serviceB)

    return new DomainService(serviceA, serviceB, serviceC);
}

That's not the case anymore with the default Lifestyle.Transient where every service would get a fresh instance of a requested dependency. Is it possible to get the SimpleInjector to simulate my hand-wiring behavior?

Thanks again!

Edit2:

I went with the SimpleInjector LifetimeScope like this:

public class CompositionRoot
{
    public static Container container;

    public sealed class DomainServiceFactory : IFactory<IDomainService>
    {
        public IDomainService Create()
        {
            var container = CompositionRoot.container;
            using (container.BeginLifetimeScope())
            {
                return container.GetInstance<IDomainService>();
            }
        }
    }

    public static void Setup(Container container)
    {
        CompositionRoot.container = container;

        container.Register<IDomainService, NeatDomainFacade>(); 
        container.Register<IFactory<IDomainService>, DomainServiceFactory>();
        container.RegisterLifetimeScope<ServiceA>();
        container.RegisterLifetimeScope<ServiceB>();
        container.RegisterLifetimeScope<ServiceC>();
    }
}

Now I can request the IFactory<IDomainService> and the lifetime scope is kicking in for the dependencies. Finally free of hand wiring :-)

役に立ちましたか?

解決

I think the problem is here that you are building up the object graphs using the contextual information for the user.

So instead of doing that, I suggest the following:

  • Prevent passing in user information through commands and through your NeatDomainFacadeFactory.Create method, but instead inject an IUserContext service that allows retrieving the IUser after the object graph is built.
  • Inject extra services that use the IUserContext to retrieve the proper information that is needed objects at runtime.

So instead of creating a service instance for one particular user, create an instance that can be used for whatever user you are running, and only make decisions based on the user when you actually send runtim data through the object graph (so after construction).

You might need some aditional refactoring to get this working though.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top