Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions nservicebus/best-practices.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Best practices
summary: An assortment of best practices presented as DO, DO NOT, and CONSIDER.
reviewed: 2024-01-05
reviewed: 2025-10-03
isLearningPath: true
---

Expand All @@ -13,9 +13,9 @@ This article presents recommendations to keep in mind when designing a system us

Multiple message handlers can be combined inside a single logical endpoint. However, these handlers all share a single message queue for different types of messages.

The endpoint is the fundamental unit of scale for an NServiceBus system. If one message handler has much higher throughput requirements, it can only be independently scaled if it exists in an endpoint by itself. In separate endpoints, only the message handler that has the unique scalability requirements has to be scaled out.
The endpoint is the fundamental unit of scale for an NServiceBus system. If one message handler has much higher throughput requirements, it can only be independently scaled if it exists in an endpoint by itself. With separate endpoints, only the message handler that has the unique scalability requirements has to be scaled out.

The endpoint is also the fundamental unit of deployment for an NServcieBus system. That means that the entire endpoint must be redeployed if a fix is required for one message handler. The fewer message handlers in each endpoint, the less likely any individual deployment is to cause a problem in the system since the whole system does not have to be redeployed on every change.
The endpoint is also the fundamental unit of deployment for an NServiceBus system. That means that the entire endpoint must be redeployed if a fix is required for one message handler. The fewer message handlers in each endpoint, the less likely any individual deployment is to cause a problem in the system since the whole system does not have to be redeployed on every change.

### :heavy_check_mark: **CONSIDER grouping message handlers by SLA**

Expand Down Expand Up @@ -43,11 +43,11 @@ Meanwhile, a custom abstraction makes the NServiceBus documentation less effecti

It is best to embrace the asynchronous nature of NServiceBus messages and not use an asynchronous message (or a pair of messages in a request/reply scenario) for synchronous communication, especially when the scenario expects an answer to be available _right now_. This is especially important with queries: [messaging should not be used for queries](https://web.archive.org/web/20211205190919/http://andreasohlund.net/2010/04/22/messaging-shouldnt-be-used-for-queries/).

When a previously-defined user interface demands an immediate response, such as inserting a new item into a grid and then immediately refreshing the grid to include the new item, the [client-side callbacks package](/nservicebus/messaging/callbacks.md) can be used, but this should be considered a crutch until a more [task- or command-focused UI](https://cqrs.wordpress.com/documents/task-based-ui/) can replace it.
When a previously defined user interface demands an immediate response, such as inserting a new item into a grid and then immediately refreshing the grid to include the new item, the [client-side callbacks package](/nservicebus/messaging/callbacks.md) can be used, but this should be considered a temporary workaround until a more [task- or command-focused UI](https://cqrs.wordpress.com/documents/task-based-ui/) can replace it.

### :x: **DO NOT create a messaging endpoint for every single web request**

NServiceBus does a lot of work when it first starts up, scanning through assemblies to find the types of messages and message handlers, establishing communication with the messaging infrastructure, and ensuring that everything is optimized to run quickly for the duration of the endpoint's life. Do not repeat all this work on every web request just to send a single message and then shut down.
NServiceBus does a lot of work when it first starts up, including scanning through assemblies to find the types of messages and message handlers, establishing communication with the messaging infrastructure, and ensuring that everything is optimized to run quickly for the duration of the endpoint's life. Do not repeat all this work on every web request just to send a single message and then shut down.

An NServiceBus endpoint is designed to be a long-lived object that persists throughout the application process. Once the `IMessageSession` is created, use dependency injection to inject it into controllers or wherever else it is needed. If necessary, assign the `IMessageSession` to a global variable.

Expand All @@ -63,7 +63,7 @@ Asynchronous messaging (e.g., NServiceBus) is **not** a good solution for data d

Message handlers should be simple and focused on only the business code needed to handle the message. Infrastructure code for logging, exception management, timing, auditing, authorization, unit of work, message signing/encryption, etc, should not be included in a message handler.

Instead, implement this functionality separately in a [message pipeline behavior](/nservicebus/pipeline/manipulate-with-behaviors.md), which enables inserting additional functionality into the NServiceBus message processing pipeline, similar to an ASP.NET ActionFilter.
Instead, implement this functionality separately in a [message pipeline behavior](/nservicebus/pipeline/manipulate-with-behaviors.md), which enables inserting additional functionality into the NServiceBus message processing pipeline.

For a high-level overview of infrastructure concerns and behaviors, see the blog post [Infrastructure soup](https://particular.net/blog/infrastructure-soup).

Expand Down Expand Up @@ -98,21 +98,17 @@ Message queues are long-lasting and durable. Occasionally connected clients, suc

For occasionally connected clients, consider another communication medium, such as in the [Near real-time transient clients sample](/samples/near-realtime-clients/), which communicates with clients using [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr).

### :heavy_check_mark: **CONSIDER [identifying message types using conventions](/nservicebus/messaging/unobtrusive-mode.md) to make upgrading to new versions of NServiceBus easier**
### :heavy_check_mark: **Consider using shared message assemblies that reference [NServiceBus.MessageInterfaces](https://www.nuget.org/packages/NServiceBus.MessageInterfaces) to make upgrading to new versions of NServiceBus easier**

By default, NServiceBus will identify classes implementing `ICommand` as commands, `IEvent` as events, and `IMessage` as other types of messages, such as replies. This is quick and easy but also causes message projects to depend on the NServiceBus NuGet package.
In a complex system, it's useful to be able to upgrade one endpoint at a time. Message assemblies are shared between multiple endpoints. If these assemblies reference the NServiceBus NuGet package directly, they can cause challenges during upgrades when one endpoint using a message assembly has upgraded to the next major version but the other has not.

In a complex system, it's useful to be able to upgrade one endpoint at a time. Message assemblies are shared between multiple endpoints, which can cause challenges during upgrades when one endpoint using a message assembly has upgraded to the next major version but the other has not.

These versioning problems can be addressed using [unobtrusive-mode messages](/nservicebus/messaging/unobtrusive-mode.md) by defining [message conventions](/nservicebus/messaging/conventions.md) independent of the `ICommand`/`IEvent`/`IMessage` interfaces.

These conventions can even be [encapsulated in a class](/nservicebus/messaging/conventions.md#encapsulated-conventions), and many can be used within one endpoint so that messages from multiple teams who have made different choices on message conventions can be used together.
To solve this, message assemblies should instead reference the [`NServiceBus.MessageInterfaces` package](/samples/message-assembly-sharing/) which has no dependency on the NServiceBus package.

## System monitoring

### :heavy_check_mark: **DO install the [Heartbeats plugin](/monitoring/heartbeats/) in all endpoints to monitor for endpoint health**
### :heavy_check_mark: **DO install the [Heartbeat plugin](/monitoring/heartbeats/) in all endpoints to monitor for endpoint health**

The Heartbeats plugin sends a message to [ServiceControl](/servicecontrol/) at regular intervals to demonstrate that the process is not only executing (which would be provided by any suite of system monitoring tools) but is also capable of interacting with the message transport.
The Heartbeat plugin sends a message to [ServiceControl](/servicecontrol/) at regular intervals to demonstrate that the process is not only executing (which would be provided by any suite of system monitoring tools) but is also capable of interacting with the message transport.

If ServiceControl stops receiving heartbeat messages from an endpoint, that endpoint will be shown as [inactive in ServicePulse](/monitoring/heartbeats/in-servicepulse.md). In addition, ServiceControl will publish a [`HeartbeatStopped` event](/monitoring/heartbeats/notification-events.md) so that operations staff can be notified and respond.

Expand Down
2 changes: 1 addition & 1 deletion nservicebus/compliance/business-continuity.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Business continuity and disaster recovery procedures
summary: Particular Software business continuity and disaster recovery procedures
reviewed: 2024-01-10
reviewed: 2025-10-03
---

This document describes Particular Software business continuity and disaster recovery procedures.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class Greeter
{
private static readonly ILog log = LogManager.GetLogger<Greeter>();
static readonly ILog log = LogManager.GetLogger<Greeter>();

public void SayHello()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Threading.Tasks;
using NServiceBus;

#region InjectingMessageSession
#region InjectingMessageSession
public class MessageSender(IMessageSession messageSession)
{
public Task SendMessage()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using System.Threading.Tasks;
using NServiceBus;
#region InjectingDependency
public class MyHandler(Greeter greeter) :
IHandleMessages<MyMessage>
#region InjectingDependency
public class MyHandler(Greeter greeter) : IHandleMessages<MyMessage>
{
public Task Handle(MyMessage message, IMessageHandlerContext context)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
using NServiceBus;

public record MyMessage : IMessage;
public record MyMessage : IMessage;
Original file line number Diff line number Diff line change
@@ -1,47 +1,38 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NServiceBus;

static class Program
{
static async Task Main()
{
Console.Title = "ExternallyManagedContainer";
Console.Title = "ExternallyManagedContainer";

var endpointConfiguration = new EndpointConfiguration("Sample");
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
endpointConfiguration.UseTransport(new LearningTransport());
var endpointConfiguration = new EndpointConfiguration("Sample");
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
endpointConfiguration.UseTransport(new LearningTransport());

#region ContainerConfiguration
#region ContainerConfiguration

// ServiceCollection is provided by Microsoft.Extensions.DependencyInjection
var serviceCollection = new ServiceCollection();
// ServiceCollection is provided by Microsoft.Extensions.DependencyInjection
var serviceCollection = new ServiceCollection();

// most dependencies may now be registered
serviceCollection.AddSingleton<Greeter>();
serviceCollection.AddSingleton<MessageSender>();
// most dependencies may now be registered
serviceCollection.AddSingleton<Greeter>();
serviceCollection.AddSingleton<MessageSender>();

// EndpointWithExternallyManagedContainer.Create accepts an IServiceCollection,
// which is inherited by ServiceCollection
var endpointWithExternallyManagedContainer = EndpointWithExternallyManagedContainer
.Create(endpointConfiguration, serviceCollection);
// EndpointWithExternallyManagedContainer.Create accepts an IServiceCollection,
// which is inherited by ServiceCollection
var endpointWithExternallyManagedContainer = EndpointWithExternallyManagedContainer
.Create(endpointConfiguration, serviceCollection);

// if IMessageSession is required as dependency, it may now be registered
serviceCollection.AddSingleton(p => endpointWithExternallyManagedContainer.MessageSession.Value);
// if IMessageSession is required as dependency, it may now be registered
serviceCollection.AddSingleton(p => endpointWithExternallyManagedContainer.MessageSession.Value);

#endregion
#endregion

using (var serviceProvider = serviceCollection.BuildServiceProvider())
{
var endpoint = await endpointWithExternallyManagedContainer.Start(serviceProvider);
using var serviceProvider = serviceCollection.BuildServiceProvider();

var sender = serviceProvider.GetRequiredService<MessageSender>();
await sender.SendMessage();
var endpoint = await endpointWithExternallyManagedContainer.Start(serviceProvider);

Console.WriteLine("Press any key to exit");
Console.ReadKey();
await endpoint.Stop();
}
}
}
var sender = serviceProvider.GetRequiredService<MessageSender>();
await sender.SendMessage();

Console.WriteLine("Press any key to exit");
Console.ReadKey();

await endpoint.Stop();
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>14.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NServiceBus" Version="10.0.0-alpha.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class Greeter
{
private static readonly ILog log = LogManager.GetLogger<Greeter>();
static readonly ILog log = LogManager.GetLogger<Greeter>();

public void SayHello()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System.Threading.Tasks;
using NServiceBus;

#region InjectingMessageSession
public class MessageSender
#region InjectingMessageSession
public class MessageSender(IMessageSession messageSession)
{
private readonly IMessageSession messageSession;

public MessageSender(IMessageSession messageSession) =>
this.messageSession = messageSession;

public Task SendMessage()
{
var myMessage = new MyMessage();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System.Threading.Tasks;
using NServiceBus;
#region InjectingDependency
public class MyHandler :
IHandleMessages<MyMessage>
#region InjectingDependency
public class MyHandler(Greeter greeter) : IHandleMessages<MyMessage>
{
private readonly Greeter greeter;

public MyHandler(Greeter greeter) =>
this.greeter = greeter;

public Task Handle(MyMessage message, IMessageHandlerContext context)
{
greeter.SayHello();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
using NServiceBus;

public class MyMessage : IMessage
{
}
public class MyMessage : IMessage;
Original file line number Diff line number Diff line change
@@ -1,47 +1,38 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NServiceBus;

static class Program
{
static async Task Main()
{
Console.Title = "ExternallyManagedContainer";
Console.Title = "ExternallyManagedContainer";

var endpointConfiguration = new EndpointConfiguration("Sample");
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
endpointConfiguration.UseTransport(new LearningTransport());
var endpointConfiguration = new EndpointConfiguration("Sample");
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
endpointConfiguration.UseTransport(new LearningTransport());

#region ContainerConfiguration
#region ContainerConfiguration

// ServiceCollection is provided by Microsoft.Extensions.DependencyInjection
var serviceCollection = new ServiceCollection();
// ServiceCollection is provided by Microsoft.Extensions.DependencyInjection
var serviceCollection = new ServiceCollection();

// most dependencies may now be registered
serviceCollection.AddSingleton<Greeter>();
serviceCollection.AddSingleton<MessageSender>();
// most dependencies may now be registered
serviceCollection.AddSingleton<Greeter>();
serviceCollection.AddSingleton<MessageSender>();

// EndpointWithExternallyManagedContainer.Create accepts an IServiceCollection,
// which is inherited by ServiceCollection
var endpointWithExternallyManagedContainer = EndpointWithExternallyManagedContainer
.Create(endpointConfiguration, serviceCollection);
// EndpointWithExternallyManagedContainer.Create accepts an IServiceCollection,
// which is inherited by ServiceCollection
var endpointWithExternallyManagedContainer = EndpointWithExternallyManagedContainer
.Create(endpointConfiguration, serviceCollection);

// if IMessageSession is required as dependency, it may now be registered
serviceCollection.AddSingleton(p => endpointWithExternallyManagedContainer.MessageSession.Value);
// if IMessageSession is required as dependency, it may now be registered
serviceCollection.AddSingleton(p => endpointWithExternallyManagedContainer.MessageSession.Value);

#endregion
#endregion

using (var serviceProvider = serviceCollection.BuildServiceProvider())
{
var endpoint = await endpointWithExternallyManagedContainer.Start(serviceProvider);
using var serviceProvider = serviceCollection.BuildServiceProvider();

var sender = serviceProvider.GetRequiredService<MessageSender>();
await sender.SendMessage();
var endpoint = await endpointWithExternallyManagedContainer.Start(serviceProvider);

Console.WriteLine("Press any key to exit");
Console.ReadKey();
await endpoint.Stop();
}
}
}
var sender = serviceProvider.GetRequiredService<MessageSender>();
await sender.SendMessage();

Console.WriteLine("Press any key to exit");
Console.ReadKey();

await endpoint.Stop();
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net9.0;net8.0;net48</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>12.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NServiceBus" Version="8.*" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public class Greeter
{
private static readonly ILog log = LogManager.GetLogger<Greeter>();
static readonly ILog log = LogManager.GetLogger<Greeter>();

public void SayHello()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System.Threading.Tasks;
using NServiceBus;

#region InjectingMessageSession
public class MessageSender
#region InjectingMessageSession
public class MessageSender(IMessageSession messageSession)
{
private readonly IMessageSession messageSession;

public MessageSender(IMessageSession messageSession) =>
this.messageSession = messageSession;

public Task SendMessage()
{
var myMessage = new MyMessage();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using System.Threading.Tasks;
using NServiceBus;
#region InjectingDependency
public class MyHandler :
IHandleMessages<MyMessage>
#region InjectingDependency
public class MyHandler(Greeter greeter) : IHandleMessages<MyMessage>
{
private readonly Greeter greeter;

public MyHandler(Greeter greeter) =>
this.greeter = greeter;

public Task Handle(MyMessage message, IMessageHandlerContext context)
{
greeter.SayHello();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
using NServiceBus;

public class MyMessage : IMessage
{
}
public class MyMessage : IMessage;
Loading