Custom routing

Component: NServiceBus | Nuget: NServiceBus (Version: 6.x)

The sample demonstrates how the routing model can be extended to allow for configuration-free routing with MSMQ transport. It does so by making endpoint instances publish metadata information about themselves:

  • identity - logical name and physical address;
  • handled messages;
  • published events.

The advantage of configuration-free approach is low development friction and simpler maintenance.

Prerequisites

  1. Make sure SQL Server Express is installed and accessible as .\SQLEXPRESS.
  2. Create database called AutomaticRouting.

Running the project

  1. Start all the projects by hitting F5.
  2. The text Press <enter> to send a message should be displayed in the Client's console window.
  3. Wait until all the endpoints exchange their routing information. Notice each endpoint logs the routing info as it discovers other endpoints.
  4. Hit <enter> several times to send some messages.

Verifying that the sample works correctly

  1. The Sales.1 and Sales.2 consoles display information about accepted orders in round-robin fashion.
  2. The Shipping endpoint displays information that orders were shipped.
  3. The Billing endpoint displays information that orders were billed.

Code walk-through

This sample contains four applications that use configuration-free custom routing:

Edit
var automaticRouting = endpointConfiguration.EnableAutomaticRouting(AutomaticRoutingConst.ConnectionString);
automaticRouting.AdvertisePublishing(typeof(OrderAccepted));
In order to use this custom routing all published types need to be specified.

Client

The Client application submits the orders for processing by the back-end systems by sending a PlaceOrder command.

Sales

The Sales application accepts clients' orders and publishes the OrderAccepted event.

In real-world scenarios NServiceBus endpoints are scaled out by deploying multiple physical instances of a single logical endpoint to multiple machines. For simplicity, in this sample the scale out is simulated by having two separate projects, Sales and Sales2.

Shipping and Billing

Shipping and Billing applications subscribe to OrderAccepted event in order to execute their business logic.

Shared project

The shared project contains definitions for messages and the custom routing logic.

Custom automatic routing

The automatic routing is based on the idea of endpoints exchanging information about types of messages they handle and types of messages they publish. In this sample they share the information using a table in SQL Server database. Each endpoint instance owns one row. However, other solutions can be used (e.g. using tool like Consul).

All the routing components are wired up using the following code:

Edit
// Create the infrastructure
var dataAccess = new SqlDataAccess(uniqueKey, connectionString);

context.Container.ConfigureComponent(
    componentFactory: builder =>
    {
        return new RoutingInfoCommunicator(dataAccess, builder.Build<CriticalError>());
    },
    dependencyLifecycle: DependencyLifecycle.SingleInstance);

context.RegisterStartupTask(
    startupTaskFactory: builder =>
    {
        return builder.Build<RoutingInfoCommunicator>();
    });

// Register the routing info publisher
context.RegisterStartupTask(startupTaskFactory: builder =>
{
    var handlerRegistry = builder.Build<MessageHandlerRegistry>();
    var messageTypesHandled = GetHandledCommands(handlerRegistry, conventions);
    return new RoutingInfoPublisher(
        dataBackplane: builder.Build<RoutingInfoCommunicator>(),
        hanledMessageTypes: messageTypesHandled,
        publishedMessageTypes: messageTypesPublished,
        settings: settings,
        heartbeatPeriod: TimeSpan.FromSeconds(5));
});

// Register the routing info subscriber
context.RegisterStartupTask(
    startupTaskFactory: builder =>
    {
        var handlerRegistry = builder.Build<MessageHandlerRegistry>();
        var messageTypesHandled = GetHandledEvents(handlerRegistry, conventions);
        var subscriber = new RoutingInfoSubscriber(
            routingTable: unicastRoutingTable,
            endpointInstances: endpointInstances,
            messageTypesHandledByThisEndpoint: messageTypesHandled,
            publishers: publishers,
            sweepPeriod: TimeSpan.FromSeconds(5),
            heartbeatTimeout: TimeSpan.FromSeconds(20));
        var communicator = builder.Build<RoutingInfoCommunicator>();
        communicator.Changed = subscriber.OnChanged;
        communicator.Removed = subscriber.OnRemoved;
        return subscriber;
    });

context.Pipeline.Register(
    stepId: "VerifyAdvertisedBehavior",
    behavior: new VerifyAdvertisedBehavior(messageTypesPublished),
    description: "Verifies if all published types has been advertised.");

It creates a publisher and a subscriber for the routing information, as well as the communication object they use to exchange information between endpoints instances. It also registers an additional behavior that ensures that all published types are properly advertised. The automatic routing discovery protocol has no master and works in a peer-to-peer manner, therefore both publisher and subscriber need to be active in each endpoint instance.

Automatic routing design

The following information is required by this automatic routing implementation:

  • Mapping command types to their logical destinations.
  • Mapping event types to their respective logical publishers.
  • Mapping logical endpoints to their physical instances.

Logical endpoints, publishers and destinations are Endpoints, while physical instances are Endpoint Instances. Refer to Endpoints article for full definitions.

This information is updated every time the automatic routing feature detects a change in the logical topology:

Edit
routingTable.AddOrReplaceRoutes("AutomaticRouting", newEndpointMap.Select(
    x => new RouteTableEntry(x.Key, UnicastRoute.CreateFromEndpointName(x.Value))).ToList());

publishers.AddOrReplacePublishers("AutomaticRouting", newPublisherMap.Select(
    x => new PublisherTableEntry(x.Key, PublisherAddress.CreateFromEndpointName(x.Value))).ToList());

endpointInstances.AddOrReplaceInstances("AutomaticRouting", newInstanceMap.SelectMany(x => x.Value).ToList());

Last modified