Extending the NServiceBus routing subsystem with a custom data source makes sense in the following scenarios:
- When centralizing all routing information in a database.
- When dynamically calculating routes using service discovery.
- When routing by a convention based on message type names.
Applying routing strategies
Routing a message involves the following steps:
- Select the routing strategy based on the message intent
- Determine the properties for the selected routing strategy based on the message type, for example, the destination address for a command routed with the unicast strategy
Command routing
To extend command routing, routing extensions can access the routing table from EndpointConfiguration or from a feature:
var settings = endpointConfiguration.GetSettings();
var routingTable = settings.GetOrCreate<UnicastRoutingTable>();
routingTable.AddOrReplaceRoutes("MySource",
new List<RouteTableEntry>
{
new RouteTableEntry(typeof(MyCommand),
UnicastRoute.CreateFromEndpointName("MyEndpoint"))
});
To access the GetSettings method, include a using directive for the NServiceBus. namespace.
The routing table can be modified in the feature setup phase or can be passed further, e.g. to a FeatureStartupTask, and updated periodically when the source of the routing information changes.
protected override void Setup(FeatureConfigurationContext context)
{
var routingTable = context.Settings.Get<UnicastRoutingTable>();
var refresherTask = new Refresher(routingTable);
context.RegisterStartupTask(refresherTask);
}
public Refresher(UnicastRoutingTable routingTable)
{
this.routingTable = routingTable;
}
protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken)
{
timer = new Timer(
callback: _ =>
{
routingTable.AddOrReplaceRoutes("MySource", LoadRoutes());
},
state: null,
dueTime: TimeSpan.FromSeconds(30),
period: TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
The sourceKey parameter is used as a unique key. When AddOrReplaceRoutes is called the first time with a given source key, the routes are added to the table. When it is called subsequently, the routes previously registered under that source key are replaced by the new routes.
The routing table API is thread-safe and atomic, meaning either all the changes from the passed collection are successfully applied or none are.
The routing system prevents route ambiguity. If new or replaced routes conflict with existing ones, AddOrReplaceRoutes throws an exception and the routing table stays unchanged. The route extension must handle the exception. Usually the safest option is to raise a critical error so the endpoint stops instead of routing messages incorrectly. By default a critical error is only logged, so the endpoint needs a critical error action that stops it.
public RobustRefresher(UnicastRoutingTable routingTable, CriticalError criticalError)
{
this.routingTable = routingTable;
this.criticalError = criticalError;
}
protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken)
{
timer = new Timer(
callback: _ =>
{
try
{
routingTable.AddOrReplaceRoutes("MySource", LoadRoutes());
}
catch (Exception exception)
{
criticalError.Raise("Ambiguous route detected", exception, cancellationToken);
}
},
state: null,
dueTime: TimeSpan.FromSeconds(30),
period: TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
Event routing
Event routing differs depending on the transport capabilities. Multicast transports which support the Publish-Subscribe pattern natively implement the event routing themselves. Refer to specific transport documentation for details on extensibility points.
Transports without that support rely on NServiceBus core routing for event delivery. The key concept is the publishers collection, which contains information on the logical endpoint that publishes each event. Routing extensions can access the publishers collection from EndpointConfiguration or from the Feature setup code:
protected override void Setup(FeatureConfigurationContext context)
{
var publishers = context.Settings.Get<Publishers>();
var publisherAddress = PublisherAddress.CreateFromEndpointName("PublisherEndpoint");
publishers.AddOrReplacePublishers(
sourceKey: "MySource",
entries: new List<PublisherTableEntry>
{
new PublisherTableEntry(typeof(MyEvent), publisherAddress)
});
}
The sourceKey parameter has the same meaning and effect as for the routing table.
The publishers collection is thread-safe, and all operations on that collection are atomic.
Advanced routing customizations
If there's a need to adjust the routing based on criteria other than the message type, the routing pipeline stage allows routing customization for all messages emitted by the endpoint.
This intercepts all dispatched messages, including messages that aren't NServiceBus message types, such as audit messages and messages sent to the error queue.