Multiple namespace support

Component: Azure Service Bus Transport
NuGet Package NServiceBus.Azure.Transports.WindowsAzureServiceBus (8.x)
Target NServiceBus Version: 7.x

Azure Service Bus transport supports configuring multiple Azure Service Bus namespaces, in order to:

  • Enable various namespace partitioning strategies to cover scenarios such as High Availability and multiple Data Center support, or to overcome Azure services limits.
  • Enable cross namespace routing to endpoints outside of the partition set.

The namespace partitioning strategy can be configured using the NamespacePartitioning() configuration API section, where the cross namespace routing can be configured using the NamespaceRouting() API section. NServiceBus provides a few namespace partitioning strategies implementations. It's also possible to implement a custom partitioning strategy if needed.

Using multiple namespace is currently NOT compatible with ServiceControl. A ServiceControl transport adapter or multiple installations of ServiceControl is required in order to leverage both.

Single namespace partitioning

By default the Azure Service Bus transport uses the SingleNamespacePartitioning strategy, when it is configured using the ConnectionString extension method:

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
transport.ConnectionString(
    connectionString: "Endpoint=sb://[NAMESPACE].servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");

This is the functional equivalent of providing a namespace using the AddNamespace partitioning API with the default namespace alias and namespace's connection string.

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var partitioning = transport.NamespacePartitioning();
partitioning.UseStrategy<SingleNamespacePartitioning>();
partitioning.AddNamespace(
    name: "default",
    connectionString: "Endpoint=sb://[NAMESPACE].servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");

With this strategy, the transport uses only a single namespace to send and receive messages, hence only one namespace can be configured for the purpose of partitioning. When more than one namespace, or none, is specified for partitioning, then a ConfigurationErrorsException will be thrown at startup.

Round robin namespace partitioning

The RoundRobinNamespacePartitioning can be used to avoid throttling by the service. With this strategy, the transport uses multiple namespaces for the communication between endpoints. Messages are sent to a single namespace and received from all namespaces. For sending operations, namespaces are chosen in a round-robin fashion.

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var partitioning = transport.NamespacePartitioning();
partitioning.UseStrategy<RoundRobinNamespacePartitioning>();
partitioning.AddNamespace(
    name: "namespace1",
    connectionString: "Endpoint=sb://namespace1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
partitioning.AddNamespace(
    name: "namespace2",
    connectionString: "Endpoint=sb://namespace2.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
partitioning.AddNamespace(
    name: "namespace3",
    connectionString: "Endpoint=sb://namespace3.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");

Multiple namespaces have to be configured when using RoundRobinNamespacePartitioning strategy. When less than two namespaces are specified, then a ConfigurationErrorsException will be thrown at startup.

Fail over namespace partitioning

The FailOverNamespacePartitioning can be used to provide High Availability. It uses two different namespaces: a primary and a secondary. The transport uses the primary namespace by default, and switches to secondary one when the primary is not available.

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var partitioning = transport.NamespacePartitioning();
partitioning.UseStrategy<FailOverNamespacePartitioning>();
partitioning.AddNamespace(
    name: "primary",
    connectionString: "Endpoint=sb://primary.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
partitioning.AddNamespace(
    name: "secondary",
    connectionString: "Endpoint=sb://secondary.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");

Exactly two namespaces have to be configured when using FailOverNamespacePartitioning strategy. When only one or more than two namespaces are specified, then a ConfigurationErrorsException will be thrown at startup.

Combining High Availability and failover options

To achieve high availability and failover, a custom strategy can be used. For example, a combination of round robin and failover strategies would ensure that messages are not throttled by the broker and sent when one of the namespaces is experiencing an outage. See custom namespace partitioning sample for details.

Caching of send/publish namespaces

Calculation of send/publish destination namespaces is an operation that is required for each dispatch message. For certain strategies performance can be improved by caching send/publish destination namespaces to be re-used for subsequent dispatches. For other strategies such as RoundRobinNamespacePartitioning namespaces should not be cached. Namespace partitioning strategies can control caching by implementing ICacheSendingNamespaces contract and setting the value of the SendingNamespacesCanBeCached property.

public class CustomNamespacePartitioningStrategy :
    INamespacePartitioningStrategy
{
    ReadOnlySettings settings;

    public CustomNamespacePartitioningStrategy(ReadOnlySettings settings)
    {
        this.settings = settings;
    }

    public IEnumerable<RuntimeNamespaceInfo> GetNamespaces(PartitioningIntent partitioningIntent)
    {
        throw new NotImplementedException();
    }

    public bool SendingNamespacesCanBeCached { get; }
}

Cross namespace routing

NServiceBus allows to specify destination addresses using an "endpoint@physicallocation" in various places such as the Send and Routing API or the MessageEndpointMappings. In this notation the physicallocation section represents the location where the endpoint's infrastructure is hosted, such as a machine name or a Service Bus namespace.

Using this notation it is possible to route messages to any endpoint hosted in namespaces that do not belong to the current endpoint's partition set.

endpointInstance.Send(
    destination: "sales@Endpoint=sb://destination1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]",
    message: new MyMessage());

Versions 7 and above of the Azure Service Bus transport it is also possible to provide an alias for namespaces, and use alias instead of connection string value; in all of these places.

endpointInstance.Send(
    destination: "sales@destination1",
    message: new MyMessage());

This requires namespace alias and connection string to be registered using the NamespaceRouting() API.

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var routing = transport.NamespaceRouting();
routing.AddNamespace(
    name: "destination1",
    connectionString: "Endpoint=sb://destination1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
routing.AddNamespace(
    name: "destination2",
    connectionString: "Endpoint=sb://destination2.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");

Versions 8 and above of the Azure Service Bus transport provide a more convenient way of sending messages over multiple namespaces that does not required to explicitly specify the destination.

To leverage that capability the namespace alias and connection string need to be registered using the NamespaceRouting() API. Furthermore, the endpoint name needs to be registered on the namespace that it belongs to.

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();

// Step 1 - associate MyMessage with "sales" endpoint (logical routing)
var routing = transport.Routing();
routing.RouteToEndpoint(typeof(MyMessage), "sales");

// Step 2 - register namespace that will be used for "sales" endpoint
var namespaceRouting = transport.NamespaceRouting();
var destination = namespaceRouting.AddNamespace(
    name: "destination1",
    connectionString: "Endpoint=sb://destination1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");

// Step 3 - Associate endpoint "sales" with the namespace to route
// MyMessage messages to (physical routing)
destination.RegisteredEndpoints.Add("sales");

With that in place a normal send operation can be issued without needing to specify the destination with an alias or a connection string.

endpointInstance.Send(message: new MyMessage());

Cross namespace subscriptions

Similar to cross namespace sending it is also possible to subscribe to registered endpoints outside the topology by registering the publisher explicitly.

var endpointOrientedTopology = transport.UseEndpointOrientedTopology();
endpointOrientedTopology.RegisterPublisher(
    type: typeof(MyEvent),
    publisherName: "sales");
This feature is only available for the EndpointOrientedTopology as the ForwardingTopology relies on the Azure Service Bus forwarding feature that does not allow forwarding between entities in different namespaces.

Default namespace alias

When using the ConnectionString method to configure a namespace, it will get an alias as well. This alias is represented by the DefaultNamespaceAlias configuration setting, which has a value of default.

When doing cross namespace request reply communication between endpoints configured this way, in combination with the UseNamespaceAliasesInsteadOfConnectionStrings() configuration method to secure connection strings, then the reply address header will include a value of "sourceendpoint@default". However, the connection string that is mapped to this alias is different for each endpoint in the communication and it will break the request-reply pattern.

In order to overcome this problem, it is possible to change the value of the DefaultNamespaceAlias configuration setting using the API:

var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
transport.DefaultNamespaceAlias("myalias");

or use NamespacePartitioning().AddNamespace() with a different alias instead of the ConnectionString() method in the source endpoint.

Also, ensure that the same alias and connection string are registered with the replying endpoint using the NamespaceRouting().AddNamespace() method.

Samples


Last modified