Injecting the backoffice into the backoffice
Heads Up!
This article is several years old now, and much has happened since then, so please keep that in mind while reading it.
First for some theory. A formal definition of a backoffice could be:
area in a bank where checks are paid, deposits and withdrawals are posted to accounts, and interest earned on deposits is credited to the account holders
When I translate this to e-commerce it happens a customer purchases something at a webshop. Then the ERP system receives the data and processes a journal entry of the purchase. And the CRM systems gets an update with the e-mail address, the product SKU and other relevant data. So the marketing department can target their e-mails campaigns.
To implement this use case I would have the following data.
<order>
<id>12345</id>
<orderDate>2013-12-23</orderDate>
<firstName>Merijn</firstName>
<lastName>van Mourik</lastName>
<email>mmourik(at)gmail(dot)com</email>
<sku>1234</sku>
</order>
After the order is finalized, I usually store it somewhere on a disk. Then at a certain point in time a batch job gets triggered and picks it up for further processing.
Wouldn't it nice if I can just send it to CRM or ERP without having to worry about making connections to other servers. Or scheduling all kind of batch jobs. What if I could have just one single command to get the data to its destination.
RegisterOrder order = new RegisterOrder();
order.id = "12345";
order.orderDate = "2013-12-23";
order.firstName = "Merijn";
order.lastName = "van Mourik";
order.email = "mmourik(at)gmail(dot)com";
order.sku = "1234";
IBus bus = ServiceBus.Bus;
bus.Send(order);
The order is just a plain old data transfer object, a class. Then the bus.Send() does the magic.
The bus.Send() can also be used from inside a MVC controller. Then it would look like this:
public class OrderConfirmationController :
Umbraco.Web.Mvc.RenderMvcController
{
public IBus bus { get; set; }
private static readonly ILog Log = LogManager
.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public override ActionResult Index(RenderModel model)
{
// .....
bus.Send(order);
// .....
}
}
With NServiceBus the bus.Send() magic comes to reality!
NServiceBus takes the responsiblity for sending data over the wire. It's just like how ethernet cards wire PC's to the local area network. You configure some addresses and NServiceBus does the job. It uses MSMQ as transport mechanism, but can use other mechanisms as well. It can also take care of scheduling and long-running processes. It just injects itself in your code, and your code stays neat and clean!
Before I dive into the code further. For me 2013 was a year when I found out about some amazing technologies. I got certified under guidance of Per and Sebastiaan. Next I built my first e-commerce website with Umbraco and TeaCommerce. I met those unicorns doing nasty stuff with HTML. Thanks Per for introducing me to AngularJs. I had loads of fun with TeaCommerce, I got certified on that too. Thanks Anders! It also was the year where I found out how to get rid of batch processing. Thanks Udi Dahan for introducing me to NServiceBus. Thanks community for building great open source software. Which makes my life fun.
Now let's get my feet back on the ground. How to inject NServiceBus right into the Umbraco controllers and event handlers? How to transport your business data to the backoffice - the easy way?
First we need to startup the bus. This is done in the startup handler of Umbraco.
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication,
ApplicationContext applicationContext)
{
ServiceBus.Init();
}
The ServiceBus.Init() uses a singleton, which ensures there is only one instance at the same time. NServiceBus needs several seconds to initialize. You only want to do this once.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NServiceBus;
namespace Umbraco.Extensions
{
public static class ServiceBus
{
public static IBus Bus { get; private set; }
public static void Init()
{
if (Bus != null)
return;
lock (typeof(ServiceBus))
{
if (Bus != null)
return;
Bus = Configure.With(AllAssemblies.Except("sqlceca40")
.And("sqlcecompact40").And("sqlceer40en").And("sqlceme40")
.And("sqlceqp40").And("sqlcese40").And("msvcr90"))
.Log4Net()
.DefaultBuilder()
.ForMVC() // <------ here is the line that
// registers everything in MVC
.UseTransport<Msmq>()
.UnicastBus()
.SendOnly();
}
}
}
}
NServiceBus uses a fluent API which makes it really easy to configure it. It's very well documented. At startup each and every dll inside the bin folder is scanned, so NServiceBus can inject itself. It uses AutoFac, but also other containers are supported. Some dll's are troublemakers, you can just Except them like I did.
The SendOnly() configuration is important. It will make sure the endpoint only sends messages, and skips some extra unneeded configuration. So everything stays as fast as possible. The receiving party does not have this, this will be a service endpoint that does full processing. But it does not have to be located at your Umbraco server. So you are really offload backoffice processing from your frontend server. NServiceBus has even load-balancing by default, so if you need heavy processing, you can easily add worker nodes too.
using System;
using System.Linq;
using NServiceBus;
using System.Web.Mvc;
using System.Web.Http;
public static class NServiceBusConfig
{
public static Configure ForMVC(this Configure configure)
{
// Register our http controller activator with NSB
configure.Configurer.RegisterSingleton(typeof(IControllerActivator),
new NServiceBusControllerActivator());
// Find every http controller class so that we can register it
var controllers = Configure.TypesToScan
.Where(t => typeof(IController).IsAssignableFrom(t));
// Register each http controller class with the NServiceBus container
foreach (Type type in controllers)
configure.Configurer.ConfigureComponent(type, DependencyLifecycle.InstancePerCall);
//Configure any other dependencies you need here
// Set the MVC dependency resolver to use our resolver
DependencyResolver.SetResolver(new NServiceBusDependencyResolverAdapter(configure.Builder));
// Required by the fluent configuration semantics
return configure;
}
}
The ForMVC() part connects NServiceBus to MVC. From where it can do it's funky stuff with dependency resolvers.
using System;
using System.Web.Mvc;
public class NServiceBusControllerActivator : IControllerActivator
{
public IController Create(System.Web.Routing.RequestContext requestContext,
Type controllerType)
{
return DependencyResolver.Current
.GetService(controllerType) as IController;
}
}
With the NServiceBusControllerActivator in place, MVC is able to resolve the dependencies ot NServiceBus.
using System;
using System.Collections.Generic;
using NServiceBus.ObjectBuilder;
using System.Web.Mvc;
public class NServiceBusDependencyResolverAdapter : IDependencyResolver
{
private IBuilder builder;
public NServiceBusDependencyResolverAdapter(IBuilder builder)
{
this.builder = builder;
}
public object GetService(Type serviceType)
{
if (typeof(IController).IsAssignableFrom(serviceType))
{
return builder.Build(serviceType);
}
return null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (typeof(IController).IsAssignableFrom(serviceType))
{
return builder.BuildAll(serviceType);
}
else
{
return new List<object>();
}
}
}
And there we have it. Apart from the controller or event handler there are 5 small files of code which do the plumbing. And it just works...
There is one little missing piece. How do CRM and ERP process the same message? This is the best part where the pub-sub pattern enters the game. Basically CRM and ERP subscribe to the message you send on the bus. And NServiceBus is just getting it done.
This all worked for me pretty well, it's a valuable extension to my toolbelt. I use it daily in a production environment where we process a lot of orders. I hope you will soon benefit from it too! I'm currently working on an example on GitHub, you can already have a look at the code. It's based upon the Hybrid Framework from Jeroen and Jeavon.
Happy coding!
Merijn
References:
Merijn van Mourik
Merijn is on Twitter as @mebrein