Mar
9
2012
There's not a lot of documentation out there on setting up MassTransit with StructureMap. It's not that difficult, but figuring out the right combination to set things up left me pulling out my considerably thinning hair. I've started with two basic projects:
1) Domain.Service - This actually holds all of the service logic, command handlers etc.... It's basically a MassTransit endpoint.
2) Domain.Service.Host - This is just a wrapper for TopShelf to host the service. I try to put as little logic in here as possible except what is necessary to start up and dispose the project.
The most important thing that happens in Domain.Service.Host is the following which occurs in the Start() method. This method is called by TopShelf to start up the service.
private void Start()
{
//Set up log4net
XmlConfigurator.Configure();
Logger.Info("setting up domain service, installing components");
_container = Bootstrapper.Bootstrap();
_bus = _container.GetInstance<IServiceBus>();
Logger.Info("application configured, started running");
}
Basically I get my StructureMap container from my Bootstrapper and then tell it to give me an instance of the bus. OK, so the important stuff is actually happening in the Bootstrapper. This and all other code displayed in this sample exists in the Domain.Service project. There's not that much to the Bootstrapper.
public static class Bootstrapper
{
public static IContainer Bootstrap()
{
ObjectFactory.Initialize(x => x.Scan(s =>
{
s.AssembliesFromApplicationBaseDirectory(y => y.FullName.StartsWith("AthLog"));
s.WithDefaultConventions();
s.LookForRegistries();
}));
ObjectFactory.Configure(x => x.For<IContainer>().Use(ObjectFactory.Container));
return ObjectFactory.Container;
}
}
So what is this doing? It's initializing Structuremap's main container. In doing so, it's telling the container to scan the application base directory for assemblies that start with our applications root namespace, in this case "AthLog". It also tells Structuremap to use default conventions. This means if it finds an interface called IFoo, it will automatically try to map it to an implementation class called Foo. This gets rid of a lot of unnecessary registration effort. Then it will also scan for Structuremap registries which we'll see further down and attempt to register anything that is specified in one of the registries. Finally I go through the odd step of registering the container with itself as the thing to return when an IContainer is asked for. You'll see the reason for this a little further down when we examine how the consumers (message handlers) are registered. There may be a more elegant way to get to this without performing this registration, I just don't know how.
So how does the actual service bus get registered? I mentioned above that the Bootstrapper scan configuration tells Structuremap to look for registries. This is an easy way to divide out your registrations into functional units so there's not so much registration code in one place to wade through. Just a way of modularizing the registration process. So also in the Domain.Service project, we have a registry class called BusRegistry.
public class BusRegistry : Registry
{
public BusRegistry()
{
For<IServiceBus>().Use(context => ServiceBusFactory.New(sbc =>
{
sbc.UseRabbitMq();
sbc.ReceiveFrom(ConfigurationManager.AppSettings[Constants.BusEndPointUri]);
sbc.UseRabbitMqRouting();
sbc.Subscribe(c => c.LoadFrom(context.GetInstance<IContainer>()));
}));
Scan(x =>
{
x.TheCallingAssembly();
x.AddAllTypesOf<IConsumer>();
});
}
}
The bus registry performs two basic tasks. The first is that it tells Structuremap how to build our bus for us. Here I'm setting it up to use RabbitMQ, setting it's receive queue and then telling it what messages it should subscribe to. Here is where that container self-registration from above comes in handy. MassTransit has built in helpers for all the major containers (available via Nuget no less). So when configured, you can tell it to set up the subscriptions by asking the container to see what kind of consumers are registered in Structuremap (or the container of your choice) when it builds a service bus for you. Because this registration code is being processed by the container, I had to put in a way to access the container itself during resolution, hence the previous self-registration. As I mentioned, there may be a better way to do this, I just don't know what it is.
Then you'll notice a second scan statement below. This is the second part of the equation that tells Structuremap to register all IConsumers (aka MassTransit message handlers) from the current assembly. The scan could be broader or more refined depending on your needs, but the idea is that all the consumers are registered with your container, then when the bus factory creates a bus it sets up all the necessary subscriptions for you. Logically this scan would make more reading sense before the service bus registration, but it doesn't matter because execution is deferred until you actually as for an instance of the IServiceBus.
So now when you ask for your bus everything is ready to go!