Multi-Instance Mode

Component: SQL Server Transport
NuGet Package NServiceBus.SqlServer (3.x)
Target NServiceBus Version: 6.x

Prerequisites

An instance of SQL Server Express is installed and accessible as .\SqlExpress.

At startup each endpoint will create its required SQL assets including databases, tables and schemas.

The databases created by this sample are NsbSamplesSqlMultiInstanceReceiver and NsbSamplesSqlMultiInstanceSender.

Ensure Distributed Transaction Coordinator (DTC) is running. It can be started from the command line by running net start msdtc.

Running the project

  1. Start both projects.
  2. Hit enter in Sender's console window to send a new message.

Verifying that the sample works correctly

  1. The Receiver displays information that an order was submitted.
  2. The Sender displays information that the order was accepted.

Code walk-through

This sample contains the following projects:

  • Sender - A console application responsible for sending the initial ClientOrder message and processing the follow-up ClientOrderAccepted message.
  • Receiver - A console application responsible for processing the order message.
  • Messages - A class library containing message definitions.

Sender project

The Sender does not store any data. It mimics the front-end system where orders are submitted by the users and passed via the bus to the back-end. It is configured to use SQL Server transport and run in the multi-instance mode. ConnectionProvider.GetConnection method is used for providing connections.

var endpointConfiguration = new EndpointConfiguration("Samples.SqlServer.MultiInstanceSender");
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.EnableLegacyMultiInstanceMode(ConnectionProvider.GetConnection);
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.EnableInstallers();

The Sender sends a message to the Receiver:

var order = new ClientOrder
{
    OrderId = Guid.NewGuid()
};
await endpoint.Send("Samples.SqlServer.MultiInstanceReceiver", order)
    .ConfigureAwait(false);

Receiver project

The Receiver mimics a back-end system. It is configured to use SQLServer transport in the multi-instance mode.

var endpointConfiguration = new EndpointConfiguration("Samples.SqlServer.MultiInstanceReceiver");
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.EnableLegacyMultiInstanceMode(ConnectionProvider.GetConnection);
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.SendFailedMessagesTo("error");
endpointConfiguration.EnableInstallers();

It receives ClientOrder message sent by Sender and replies to them with ClientOrderAccepted.

public Task Handle(ClientOrder message, IMessageHandlerContext context)
{
    log.Info($"Handling ClientOrder with ID {message.OrderId}");
    var clientOrderAccepted = new ClientOrderAccepted
    {
        OrderId = message.OrderId
    };
    return context.Reply(clientOrderAccepted);
}

Multi-instance connection lookup

Both sender and receiver provide a custom lookup mechanism for providing connection information for given destination. The following snippet shows lookup logic used by Sender.

static class ConnectionProvider
{
    public const string DefaultConnectionString = @"Data Source=.\SqlExpress;Database=NsbSamplesSqlMultiInstanceSender;Integrated Security=True;Max Pool Size=100";
    const string ReceiverConnectionString = @"Data Source=.\SqlExpress;Database=NsbSamplesSqlMultiInstanceReceiver;Integrated Security=True;Max Pool Size=100";

    public static async Task<SqlConnection> GetConnection(string transportAddress)
    {
        string connectionString;
        if (transportAddress.StartsWith("Samples.SqlServer.MultiInstanceReceiver"))
        {
            connectionString = ReceiverConnectionString;
        }
        else
        {
            connectionString = DefaultConnectionString;
        }

        var connection = new SqlConnection(connectionString);

        await connection.OpenAsync()
            .ConfigureAwait(false);

        return connection;
    }
}

How it works

Sender and Receiver use different catalogs on the same SQL Server instance. The tables representing queues for a particular endpoint are created in the appropriate catalog, i.e. in NsbSamplesSqlMultiInstanceReceiver for the Receiver endpoint and in NsbSamplesSqlMultiInstanceSender for the Sender endpoint. It is possible to register a custom SqlConnection factory that provides connection instance per given transport address. The operations performed on queues stored in different catalogs are atomic because SQL Server allows multiple SqlConnection enlisting in a single distributed transaction.

In this sample DTC is required by the Receiver because it operates on two different catalogs when receiving Sender's request. It picks message from input queue stored in NsbSamplesSqlMultiInstanceReceiver and sends back reply to Sender's input queue stored in NsbSamplesSqlMultiInstanceSender. In addition error queue is stored also in NsbSamplesSqlMultiInstanceSender so without DTC Receiver will not be able handle failed messages properly.

Related Articles


Last modified