Tips for Defining NServiceBus Conventions

September 29, 2025#Software Architecture
Series: NServiceBus
Article
Author image.

Kyle McMaster, Architect

This post is part of a series on NServiceBus. In the previous articles, we looked at fundamental concepts for messaging and how to get started with NServiceBus. In this article, we explore a lesser known feature of NServiceBus: defining conventions to identify message types. I recently learned about this feature while exploring integrations between NServiceBus and MassTransit. I immediately saw a use-case for the context of Modular Monolith Architecture with message contracts defined in a shared library without direct dependencies on NServiceBus. This article shares some tips and examples for defining conventions in NServiceBus.

What are NServiceBus Conventions? 🧐

Out of the box, NServiceBus uses conventions to determine which classes are messages, commands, or events as well as other default behaviors for message systems. For example, NServiceBus treats any class that implements the IMessage marker interface as a message (not a command or event). Conventions allow you to customize this behavior to fit your project’s needs.

Why define custom conventions? 🙋‍♂️

Defining custom conventions can help you align NServiceBus with your project’s architecture and coding standards. You may be using your own marker interfaces, attributes, or naming patterns to identify messages, commands, and events. If you are enforcing a very strict approach to Clean Architecture, this can help ensure that your messaging types defined in the Core layer don’t take a direct dependency on NServiceBus (although this have never been a problem in my experience 😁). Alternatively, you may be consuming messages from a third-party system or Modular Monolith Architecture and you want to ensure compatibility for scenarios like handling MassTransit messages with NServiceBus (which we will cover in a future article 😉). If you’re using a shared library for message contracts, it’s likely those types don’t implement NServiceBus interfaces. Using a custom convention allows you to integrate these message contracts seamlessly into an NServiceBus system.

Some example conventions 👀

You can define custom conventions in the EndpointConfiguration from the Conventions method just like you would configure other endpoint settings like Routing and Transport. Let’s assume you have a shared library with messages defined in the BillingModule.Contracts.Messages namespace and nested namespaces for commands and events. The following is a convention to find messages of each type based on the namespace.

var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
var conventions = endpointConfiguration.Conventions();

conventions
  conventions.DefiningCommandsAs(type => type.Namespace == "BillingModule.Contracts.Messages.Commands");
  conventions.DefiningEventsAs(type => type.Namespace == "BillingModule.Contracts.Messages.Events");
  conventions.DefiningMessagesAs(type => type.Namespace == "BillingModule.Contracts.Messages");

This example uses simple namespace checks, but you can use any logic you want. It’s important to note that this approach also overrides the default behavior of NServiceBus, so if you want to keep the default behavior and add to it, you can combine your custom logic with the existing checks. An example for commands is shown in the next code snippet.

conventions.DefiningCommandsAs(type =>
  type.Namespace == "BillingModule.Contracts.Messages.Commands" ||
  typeof(ICommand).IsAssignableFrom(type)); // <- keep default behavior

Since maintaining NServiceBus’s default behavior as well as custom logic can be challenging, you can also implement a IMessageConvention class which doesn’t override the default behavior. It also makes sense to encapsulate custom logic in one place for reuse and keeps your endpoint configuration cleaner.

public static class CustomMessageConvention : IMessageConvention
{
  public string Name { get; } = "My Custom Message Convention for BillingModule";

  public static bool IsCommandType(Type type) =>
    type.Namespace == "BillingModule.Contracts.Messages.Commands";

  public bool IsEventType(Type type) =>
    type.Namespace == "BillingModule.Contracts.Messages.Events";

  public bool IsMessageType(Type type) =>
    type.Namespace == "BillingModule.Contracts.Messages";
}

var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
var conventions = endpointConfiguration.Conventions();
conventions.Add(new CustomMessageConvention());

Practical tips ✔️

  • Keep It Simple: Be sure to start with simple conventions and evolve them as needed. Always remember to test your conventions thoroughly to ensure they work as expected.
  • Consistency: Ensure that your conventions are consistently applied across your endpoints where appropriate. This will help ensure messages are correctly identified and processed as intended.
  • Documentation: Document your conventions clearly so that all team members understand. It’s easy to miss or forget about conventions when new team members join so make sure to communicate them effectively. 📚

Conclusion 🧠

Conventions can be a helpful tool for building flexible systems that understand your architecture, service ecosystem, and coding standards. They can help you avoid direct dependencies on NServiceBus in your message contracts and make it easier to integrate with third-party systems that may be using other messaging patterns or frameworks. By defining clear and consistent conventions, you can ensure that your messaging types are correctly identified and processed, leading to a more maintainable and robust system. Happy messaging! 💌

Resources 📕


Copyright © 2025 NimblePros - All Rights Reserved