Unobtrusive Mode Messages

When using NServiceBus you define your message contracts using plain classes or interfaces. For NServiceBus to find those classes when scanning your assemblies you need to mark them with the special IMessage interface, which essentially says, "Hey, this is a message definition, please use it." This might seem like a small thing but now you're coupling your message contracts to a NServiceBus assembly since you need to reference the NServiceBus.dll to get access to the interface.

This dependency can cause problems if you have different services that run different versions of NServiceBus. Jonathan Oliver has a great write up on this very subject.

This is not a big deal for commands because they are always used with in the boundary of a single service and it's fair to require a service to use the same version of NServiceBus. But when it comes to events, this becomes more of a problem since it requires your services to all use the same version of NServiceBus, thereby forcing them to upgrade NServiceBus all at once.

The solution

There are a couple of ways you can solve this.

  • NServiceBus follows the semver.org semantics, only changing the assembly version when changes are not backwards compatible or introduce substantial new functionality or improvements. This mean that version 3.0.1 and version 3.0.X have the same assembly version (3.0.0), and file version of course changes for every release/build. This means that as long as you do a NuGet update with the -safe flag your service contracts will stay compatible.
  • Support for running in "Unobtrusive" mode means you do not need to reference any NServiceBus assemblies from your own message assemblies, thereby removing the problem altogether.

Unobtrusive mode

NServiceBus allows you to define your own message conventions instead of using the IMessage, ICommand or IEvent interfaces and attributes like TimeToBeReceivedAttribute and ExpressAttribute. NServiceBus also supports conventions for encrypted properties, express messages, databus properties and time to be received. So with these conventions combined you can avoid referencing NServiceBus in your messages assembly.

// NOTE: When you're self hosting, '.DefiningXXXAs()' has to be before '.UnicastBus()', 
// otherwise you'll get: 'System.InvalidOperationException: "No destination specified for message(s): MessageTypeName"

Configure configure = Configure.With();

configure.DefiningCommandsAs(t => t.Namespace == "MyNamespace" && t.Namespace.EndsWith("Commands"));
configure.DefiningEventsAs(t => t.Namespace == "MyNamespace" && t.Namespace.EndsWith("Events"));
configure.DefiningMessagesAs(t => t.Namespace == "Messages");
configure.DefiningEncryptedPropertiesAs(p => p.Name.StartsWith("Encrypted"));
configure.DefiningDataBusPropertiesAs(p => p.Name.EndsWith("DataBus"));
configure.DefiningExpressMessagesAs(t => t.Name.EndsWith("Express"));
configure.DefiningTimeToBeReceivedAs(t => t.Name.EndsWith("Expires") ? TimeSpan.FromSeconds(30) : TimeSpan.MaxValue);
BusConfiguration busConfiguration = new BusConfiguration();
ConventionsBuilder conventions = busConfiguration.Conventions();
conventions.DefiningCommandsAs(t => t.Namespace != null && t.Namespace == "MyNamespace" && t.Namespace.EndsWith("Commands"));
conventions.DefiningEventsAs(t => t.Namespace != null && t.Namespace == "MyNamespace" && t.Namespace.EndsWith("Events"));
conventions.DefiningMessagesAs(t => t.Namespace != null && t.Namespace == "Messages");
conventions.DefiningEncryptedPropertiesAs(p => p.Name.StartsWith("Encrypted"));
conventions.DefiningDataBusPropertiesAs(p => p.Name.EndsWith("DataBus"));
conventions.DefiningExpressMessagesAs(t => t.Name.EndsWith("Express"));
conventions.DefiningTimeToBeReceivedAs(t => t.Name.EndsWith("Expires") ? TimeSpan.FromSeconds(30) : TimeSpan.MaxValue);


Last modified 2015-08-31 11:41:37Z