NServiceBus Message Modules (IMessageModule)

When handling messages in NServiceBus it may become necessary to consume the same message multiple times but in different ways. For example, suppose you wanted to update separate yet related tables in the database. You could throw all of the business logic into a single message handler and be done with it. The only problem is that this message handler would be performing multiple actions and the intention behind the handler would be obscured. Instead, create multiple, distinct message handlers and have each perform one complete action related to the message, e.g. updating a table and publishing a message. In this way it becomes much easier to reason about each handler independently.

Logical vs. Physical Messages

When NServiceBus receives a message from a message queue, it receives what is known as a TransportMessage. The TransportMessage can be viewed as a message envelope of sorts. It holds multiple business/application messages and gives some metadata about its return address, correlation identifier, deliverability guarantees, and time to be received.

NServiceBus receives a TransportMessage from the queue and deserializes it, typically using the default XmlSerializer. Deserializing this physical message yields a collection of logical business/application messages. Each logical message is then dispatched to the list of registered handlers.

But what if we wanted to wrap all of the handlers that we're executing? What if we wanted to have some behavior occur prior to the handlers receiving messages and after all handler code was complete? We need message modules.

Using Message Modules

At the most basic level a message module is a .NET class that implements the IMessageModule interface which is found in the NServiceBus.dll assembly. This interface exposes three parameterless methods: HandleBeginMessage(), HandleEndMessage(), and HandleError(). These methods are invoked exactly once per physical TransportMessage received. In a typical scenario the only methods invoked will be HandleBeginMessage() and HandleEndMessage().

Message modules are designed to wrap the receipt of a physical message. They allow you to set values and invoke behavior during the initial stages of receiving a message and to perform any cleanup after all handlers have executed. Lastly, they allow you to invoke additional behavior in failure scenarios.

The canonical example of implementing a message module is when working with NHibernate's ISession and ISessionFactory. During HandleBeginMessage(), we create a new session and bind that session to the thread, e.g.

CurrentSessionContext.Bind(sessionFactory.OpenSession());

This allows each message handler executing on the same thread to obtain the same instance of ISession and thus participate in the same unit of work when interacting with a database. When all logical messages have been handled NServiceBus will invoke the module's HandleEndMessage() method where we can unbind the current session from the thread and perform additional clean as necessary.

Message Module Behavior

There are a few things to be aware of when implementing a message module. The first is that message modules are global or singleton scoped. When the bus is started it requests the set of configured message modules from the configured IoC container. This set of modules is then held and stored for the duration of the bus lifetime.

Because a message module is singleton scoped, care must be taken not to store message-specific state directly in the module. Instead, this information should be stored on the thread performing the processing by decorating the associated static member variable with the TheadStatic attribute. In addition NServiceBus reuses threads. This means that all values stored on the thread using the ThreadStatic attribute must be cleared and reinitialized at the start of processing a new message. Failure to reset all state within a module may lead to subtle bugs in your infrastructure code.

Multiple message modules can be registered to execute. Simply have each implement IMessageModule. NServiceBus will automatically scan all assemblies in the runtime directory and register any classes that implement IMessageModule. Generally, the ordering of message modules should be unimportant to your application code. Nonetheless, you may explicitly control the ordering of the message modules by registering each with your IoC container explicitly and in the order that best fits your requirements. Message modules are always invoked from first to last. This means that when HandleBeginMessage() is called, it is called on the first registered module then on the second registered module and so on. This same ordering—first to last—is maintained when invoking HandleEndMessage() and HandleError().

When a message module is registered with the bus, at least two of the three methods will always be invoked—HandleBeginMessage() and HandleEndMessage(). In error or failure conditions these two methods will be invoked and then HandleError() will be invoked. This means that there isn't a "Finally()" or "Dispose()" method for each module. That is, there's no way to be sure that HandleEndMessage() is the last method to be invoked because it may be followed by HandleError() in failure conditions.

Conclusion

Despite a few minor quirks in the current implementation message modules serve a valuable purpose inside of an application and can be leveraged when the situation dictates their use, just like any other tool in the expansive NServiceBus toolbelt.