Multi-Instance Mode

Component: SQL Server Transport
NuGet Package NServiceBus.SqlServer (3.x)
Target NServiceBus Version: 6.x
In SQL Server transport version 4, multi-instance mode has been deprecated. The migration sample explains how to use the transport bridge instead.

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. Press enter in the 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

The 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 a front-end system where orders are submitted by the users and passed via the bus to the back-end. It is configured to use the SQL Server transport and run in 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 the SQL Server transport in 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 messages 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 the sender and receiver provide a custom lookup mechanism for providing connection information for a given destination. The following snippet shows lookup logic used by the 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

The 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 a request from the sender. It picks a message from the input queue stored in NsbSamplesSqlMultiInstanceReceiver and sends a reply to the sender's input queue stored in NsbSamplesSqlMultiInstanceSender. In addition the error queue is also stored in NsbSamplesSqlMultiInstanceSender so without DTC, the receiver will not be able to handle failed messages properly.

Related Articles


Last modified