I am using Simple Injector to manage the lifetime of my injected dependencies (in this case UnitOfWork
), and I am very happy as having a separate decorator rather than my service or command handler looking after saving and disposing makes code a lot easier when writing business logic layers (I follow the architecture that is outlined in this blog post).
The above is working perfectly (and very easily) by using the Simple Injector MVC NuGet package and the following code during the construction of the composition root container, if more than one dependency exists in the graph the same instance is injected across all - perfect for Entity Framework model context.
private static void InitializeContainer(Container container)
{
container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();
// register all other interfaces with:
// container.Register<Interface, Implementation>();
}
I now need to run some background threads and understand from Simple Injector documentation on threads that commands can be proxied as follows:
public sealed class TransactionCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> handlerToCall;
private readonly IUnitOfWork unitOfWork;
public TransactionCommandHandlerDecorator(
IUnitOfWork unitOfWork,
ICommandHandler<TCommand> decorated)
{
this.handlerToCall = decorated;
this.unitOfWork = unitOfWork;
}
public void Handle(TCommand command)
{
this.handlerToCall.Handle(command);
unitOfWork.Save();
}
}
ThreadedCommandHandlerProxy:
public class ThreadedCommandHandlerProxy<TCommand>
: ICommandHandler<TCommand>
{
Func<ICommandHandler<TCommand>> instanceCreator;
public ThreadedCommandHandlerProxy(
Func<ICommandHandler<TCommand>> creator)
{
this.instanceCreator = creator;
}
public void Handle(TCommand command)
{
Task.Factory.StartNew(() =>
{
var handler = this.instanceCreator();
handler.Handle(command);
});
}
}
However, from this threading sample documentation I can see factories are used, if I introduce factories to my commands and service layer things will get confused and inconsistent as I will have different saving methodologies for different services (one container handles saving, other instantiated factories within services handle saves and disposing) - you can see how clear and simple the service code skeleton is without any factories:
public class BusinessUnitCommandHandlers :
ICommandHandler<AddBusinessUnitCommand>,
ICommandHandler<DeleteBusinessUnitCommand>
{
private IBusinessUnitService businessUnitService;
private IInvoiceService invoiceService;
public BusinessUnitCommandHandlers(
IBusinessUnitService businessUnitService,
IInvoiceService invoiceService)
{
this.businessUnitService = businessUnitService;
this.invoiceService = invoiceService;
}
public void Handle(AddBusinessUnitCommand command)
{
businessUnitService.AddCompany(command.name);
}
public void Handle(DeleteBusinessUnitCommand command)
{
invoiceService.DeleteAllInvoicesForCompany(command.ID);
businessUnitService.DeleteCompany(command.ID);
}
}
public class BusinessUnitService : IBusinessUnitService
{
private readonly IUnitOfWork unitOfWork;
private readonly ILogger logger;
public BusinessUnitService(IUnitOfWork unitOfWork,
ILogger logger)
{
this.unitOfWork = unitOfWork;
this.logger = logger;
}
void IBusinessUnitService.AddCompany(string name)
{
// snip... let container call IUnitOfWork.Save()
}
void IBusinessUnitService.DeleteCompany(int ID)
{
// snip... let container call IUnitOfWork.Save()
}
}
public class InvoiceService : IInvoiceService
{
private readonly IUnitOfWork unitOfWork;
private readonly ILogger logger;
public BusinessUnitService(IUnitOfWork unitOfWork,
ILogger logger)
{
this.unitOfWork = unitOfWork;
this.logger = logger;
}
void IInvoiceService.DeleteAllInvoicesForCompany(int ID)
{
// snip... let container call IUnitOfWork.Save()
}
}
With the above my problem starts to form, as I understand from the documentation on ASP .NET PerWebRequest lifetimes, the following code is used:
public T GetInstance()
{
var context = HttpContext.Current;
if (context == null)
{
// No HttpContext: Let's create a transient object.
return this.instanceCreator();
}
object key = this.GetType();
T instance = (T)context.Items[key];
if (instance == null)
{
context.Items[key] = instance = this.instanceCreator();
}
return instance;
}
The above works fine for each HTTP request there will be a valid HttpContext.Current
, however if I spin-up a new thread with the ThreadedCommandHandlerProxy
it will create a new thread and the HttpContext
will no longer exist within that thread.
Since the HttpContext
would be null on each subsequent call, all instances of objects injected into service constructors would be new and unique, the opposite to normal HTTP per web request where objects are shared correctly as the same instance across all services.
So to summarize the above into questions:
How would I go about getting the objects constructed and common items injected regardless of whether created from HTTP request or via a new thread?
Are there any special considerations for having a UnitOfWork
managed by a thread within a command handler proxy? How can one ensure it is saved and disposed of after the handler has executed?
If we had a problem within the command-handler/service-layer and didn't want to save the UnitOfWork
, would we simply throw an exception? If so, is it possible to catch this at a global level or do we need to catch the exception per request from within a try
-catch
in the handler decorator or proxy?
Thanks,
Chris
Let me start of by warning that if you wish to execute commands asynchronously in a web application, you might want to take a step back and look at what you are trying to achieve. There is always the risk of the web application being recycled just after you started your handler on a background thread. When a ASP.NET app gets recycled, all background threads will be aborted. It would perhaps be better to publish commands to a (transactional) queue and let a background service pick them up. This ensures that commands can't 'get lost'. And also allows you to re-execute commands when the handler does not succeed successfully. It can also save you from having to do some nasty registrations (which you will probably have no matter which DI framework you pick), but this probably just a side issue. And if you do need to run handlers async, at least try to minimize the number of handlers that you run async.
With that out of the way, what you need is the following.
As you noted, since you are running (some) command handlers asynchronously, you can't use the per web request lifestyle on them. You will need a hybrid solution, that mixes between per web request and 'something else'. That something else will most likely be a per lifetime scope. There are no built-in extensions for these hybrid solutions, because of a couple reasons. First of all it's quite an exotic feature that not many people need. Second, you can mix any two or three lifestyles together, so that would be almost an endless combination of hybrids. And last, it is (pretty) easy do register this yourself.
In Simple Injector 2, the Lifestyle
class has been added and it contains a CreateHybrid
method that allows combining any two lifestyles to create a new lifestyle. Here's an example:
var hybridLifestyle = Lifestyle.CreateHybrid(
() => HttpContext.Current != null,
new WebRequestLifestyle(),
new LifetimeScopeLifestyle());
You can use this hybrid lifestyle to register the unit of work:
container.Register<IUnitOfWork, DiUnitOfWork>(hybridLifestyle);
Since you are registering the unit of work as Per Lifetime Scope, you must explicitly create and dispose a Lifetime Scope for a certain thread. The simplest thing to do is to add this to your ThreadedCommandHandlerProxy
. This is not the most SOLID way of doing things, but it is the easiest way for me to show you how to do this.
If we had a problem within the command-handler/service-layer and didn't want to save the UnitOfWork, would we simply throw an exception?
The typical thing to do is to throw an exception. It's in fact the general rule of exceptions:
If your method can't do what it's name promises it can, throw. ->
The command handler should be ignorant about how the context in which it is executed, and the last thing you want is to differentiate in whether it should throw an exception. So throwing is your best option. When running on a background thread however, you'd better catch that exception, since .NET will kill the whole AppDomain, if you don't catch it. In a web application this means a AppDomain recycle, which means you're web application (or at least that server) will be offline for a short period of time.
On the other hand, you also don't want to lose any exception information, so you should log that exception, and probably want to log a serialized representation of that command with that exception, so you can see what data was passed in. When added to the ThreadedCommandHandlerProxy.Handle
method, the it would look like this:
public void Handle(TCommand command)
{
string xml = this.commandSerializer.ToXml(command);
Task.Factory.StartNew(() =>
{
var logger =
this.container.GetInstance<ILogger>();
try
{
using (container.BeginTransactionScope())
{
// must be created INSIDE the scope.
var handler = this.instanceCreator();
handler.Handle(command);
}
}
catch (Exception ex)
{
// Don't let the exception bubble up,
// because we run in a background thread.
this.logger.Log(ex, xml);
}
});
}
I warned about how running handlers asynchronously might not be the best idea. However, since you are applying this command / handler pattern, you will be able to switch to using queuing later on, without having to alter a single line of code in your application. It's just a matter of writing some sort of QueueCommandHandlerDecorator<T>
(which serializes the command and sends it to the queue) and change the way things are wired in your composition root, and you're good to go (and of course don't forget to implement the service that executes commands from the queue). In other words, what's great about this SOLID design is that implementing these features is constant to the size of the application.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With