Yes I know I promised you guys amazing things are happening in NServiceBus. But I want to start with the small things and then go over some bigger changes. So feel free to skip this blog post if I disappointed you 😉 NServiceBus 3.0 and greater has the notion of an unit of work. The unit of work in NServiceBus allows you to externalize repetitive code such as committing NHibernate transactions, calling SaveChanges on a RavenDB session and much more. You can create your own unit of work pretty easily by just implementing the IManagesUnitOfWork interface and registering it in the provided container abstraction of NServiceBus. Let us explore an example…
Imagine you were using RavenDB in your application to store your application information. When you use RavenDB you have to basically do the following (if you hadn’t the unit of work) in every handler:
public class InvoiceReceivedHandler : IMessageHandler<InvoiceMessage> { private readonly IDocumentSession documentSession; public InvoiceReceivedMessageHandler(IDocumentSession documentSession) { this.documentSession = documentSession; } public void Handle(InvoiceMessage message) { Invoice invoice = message.ToInvoice(); this.documentSession.Store(invoice); this.documentSession.SaveChanges(); } }
That does look innocent first. But having to call save changes quickly spreads all over your message handlers. What happens if someone forgets to call it? And even more important is that multiple message handlers could be called in a pipeline in the same transaction scope and we actually want to save the changes only if the whole pipeline ran through. Here is where IManagesUnitOfWork kicks in and shines!
We simply define a RavenUnitOfWork which looks like:
public class RavenUnitOfWork : IManageUnitsOfWork { private readonly IDocumentSession session; public RavenUnitOfWork(IDocumentSession session) { this.session = session; } public void Begin() { } public void End(Exception ex) { if (ex == null) { this.session.SaveChanges(); } } }
So the RavenUnitOfWork gets a document session injected and if there was no exception in the message handler pipeline SaveChanges is called on that session. NServiceBus ensures that Begin() will be called when the transport message enter the pipeline and the End() method will be called when the processing is complete. If there was any exception during that process the exception will be passed into the End() method. But how do we tell NServiceBus to use our RavenUnitOfWork?
If you are using NServiceBus Configuration Container approach you can simply use the following syntax:
config.Configurer.ConfigureComponent<RavenUnitOfWork>(DependencyLifecycle.InstancePerCall); // slighlty simplified sample here! config.Configurer.ConfigureComponent<DocumentSession>(DependencyLifecycle.InstancePerUnitOfWork);
But because my applications dependencies are managed with ninject I don’t like using the NServiceBus Configuration Container. I tend to work directly with ninject using ninjects binding modules. If you take the naive route you end up declaring your ninject module like the following:
public class RavenDatabaseModule : NinjectModule { public override void Load() { this.Bind<IDocumentStore>().To<DocumentStore>().InSingletonScope().OnActivation( store => { store.Url = "http://localhost:8080"; store.DefaultDatabase = Constants.DatabaseName; store.Initialize(); store.DatabaseCommands.EnsureDatabaseExists(Constants.DatabaseName); }); this.Bind<IDocumentSession>().ToMethod(ctx => ctx.Kernel.Get<IDocumentStore>().OpenSession()); this.Bind<IAsyncDocumentSession>().ToMethod(ctx => ctx.Kernel.Get<IDocumentStore>().OpenAsyncSession()); this.Bind<IManageUnitsOfWork>().To<RavenUnitOfWork>(); } }
Then you are in trouble! No it’s not because I hard coded the connection URI and the database 😉 There is actually a whole other issue now going on in your application. Your RavenUnitOfWork will get another IDocumentSession instance injected than your message handler (in our example the InvoiceReceivedHandler)! So calling SaveChanges on a session instance which is not the same one your handlers stored data is tracked, will actually lead to you looking for the data in your RavenDB and you will never find it!
So apart from NServiceBus 4.0.0 it will be possible with the ninject object builder to participate in the unit of work scope which is defined by NServiceBus. For that purpose we have provided some simple extension methods. You just have to add a using to the following namespace NServiceBus.ObjectBuilder.Ninject. Then you can do the following:
using NServiceBus.ObjectBuilder.Ninject; public class RavenDatabaseModule : NinjectModule { public override void Load() { // Other stuff left out... this.Bind<IDocumentSession>().ToMethod(ctx => ctx.Kernel.Get<IDocumentStore>().OpenSession()) .InUnitOfWorkScope(); this.Bind<IAsyncDocumentSession>().ToMethod(ctx => ctx.Kernel.Get<IDocumentStore>().OpenAsyncSession()) .InUnitOfWorkScope();; // Other stuff left out... } }
You can declare InUnitOfWorkScope and your session will automatically be scoped correctly. If you have other code which relies on connection to your RavenDatabase which needs another scope (so the code is practically living outside a NSB unit of work) we provide conditional binding methods to declare more intelligent bindings. For example you could do the following:
public class RavenDatabaseModule : NinjectModule { public override void Load() { // Other stuff left out... this.Bind<IDocumentSession>().ToMethod(ctx => ctx.Kernel.Get<IDocumentStore>().OpenSession()) .WhenInUnitOfWorkScope() .InUnitOfWorkScope(); this.Bind<IDocumentSession>().ToMethod(ctx => ctx.Kernel.Get<IDocumentStore>().OpenSession()) .WhenNotInUnitOfWork() .InThreadScope(); // Other stuff left out... } }
Can we translate that in human understandable phrases? Of course we can. This would instruct Ninject to scope the IDocumentSession instances in the NSB unit of work as long as a session is requested inside a unit of work and all its requests in the pipeline. Or if a session would be retrieved outside a message handler pipeline (for example in a component with a different lifetime) it would be scoped per thread (so each thread requesting IDocumentSession would get a new instance of IDocumentSession). Nice isn’t it?
Is this only a problem with Ninject? I have non of such problems with Castle Windsor and Unity. Due to what behavior is this solution required for Ninject?
RT @planetgeekch: NServiceBus UnitOfWorkScope with Ninject http://t.co/nEMMCsMX
@Ramon
The scoping is done by using the ninject named scope extension. This requires to define a named scope on the root object. Bindings participating in that scope need to declare it in the binding. I cannot speak for the other frameworks but I think the use a different mechanism. If you want to know more about the current implementation I can hook up Remo Gloor to this conversation.