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 QueryHandler
s/CommandHandler
s 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 :-)