Hosting
NServiceBus Messaging Bridge is hosted using the .NET Generic Host which takes care of life cycle management, configuration, logging, and other concerns.
await Host.CreateDefaultBuilder()
.UseNServiceBusBridge(bridgeConfiguration =>
{
// Configure the bridge
})
.Build()
.RunAsync();
The overload that accepts a HostBuilderContext
provides access to the IConfiguration
type and other host related details.
await Host.CreateDefaultBuilder()
.UseNServiceBusBridge((hostBuilderContext, bridgeConfiguration) =>
{
var connectionString = hostBuilderContext.Configuration.GetValue<string>("MyBridge:AzureServiceBusConnectionString");
var concurrency = hostBuilderContext.Configuration.GetValue<int>("MyBridge:Concurrency");
var transport = new BridgeTransport(new AzureServiceBusTransport(connectionString))
{
Concurrency = concurrency
};
bridgeConfiguration.AddTransport(transport);
// more configuration...
})
.Build()
.RunAsync();
Registering endpoints
If a logical endpoint communicates with other endpoints that use a different transport, it must be registered with the bridge. Endpoints are registered with the bridge on the transport they run on. The bridge then creates a proxy endpoint on each transport that needs to be bridged.
await Host.CreateDefaultBuilder()
.UseNServiceBusBridge((ctx, bridgeConfiguration) =>
{
var msmq = new BridgeTransport(new MsmqTransport());
msmq.HasEndpoint("Sales");
msmq.HasEndpoint("Shipping");
var asb = new BridgeTransport(new AzureServiceBusTransport(connectionString));
asb.HasEndpoint("Finance.Invoicing");
asb.HasEndpoint("Finance.Billing");
bridgeConfiguration.AddTransport(msmq);
bridgeConfiguration.AddTransport(asb);
})
.Build()
.RunAsync();
Having the same logical endpoint running on both transports is not supported. The bridge will throw an exception if an endpoint is registered on more than one transport.
If a duplicate endpoint it is not registered with the bridge, any messages it sends that need to be forwarded across the bridge will fail and get sent to the bridge error queue.
Registering publishers
When NServiceBus discovers a message handler in an endpoint for an event, it automatically subscribes to this event on the transport. The publisher itself is not aware of this, since it does not receive a notification when a subscriber subscribes to an event. This represents a challenge for the bridge.
If an endpoint subscribes to an event, the bridge must be made aware of this subscription since it must register the same subscription on the transports it's bridging. As the bridge is unaware of any subscriptions, the bridge must be configured to mimic the behavior of the endpoints.
The result is duplicate subscriptions for any endpoint that subscribes to an event. The endpoint that publishes the event must be configured as well.
var msmq = new BridgeTransport(new MsmqTransport());
msmq.HasEndpoint("Sales");
msmq.HasEndpoint("Finance.Billing");
var asb = new BridgeTransport(new AzureServiceBusTransport(connectionString));
asb.HasEndpoint("Shipping");
var invoicing = new BridgeEndpoint("Finance.Invoicing");
invoicing.RegisterPublisher(typeof(OrderBilled), "Finance.Billing");
invoicing.RegisterPublisher<OrderShipped>("Shipping");
invoicing.RegisterPublisher("Messages.OrderPlaced", "Sales");
asb.HasEndpoint(invoicing);
The endpoint name used when creating a BridgeEndpoint
is case-sensitive, even if one or both transports don't require it. This is to accommodate all transports, some of which require a case-sensitive endpoint name. More details can be found on this issue.
Legacy transport versions that use message-driven pub/sub require the fully qualified assembly type name value to be passed. Note that passing the culture and public key is not needed -- only the type name, assembly name, and assembly version are used in filtering subscribers by the message-driven pub/sub-feature.
// Type.AssemblyQualifiedName Property value
invoicing.RegisterPublisher("CreditApproved, CreditScoring.Messages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Sales");
// Type.AssemblyQualifiedName Property but trimmed without Culture and PublicKeyToken as these are ignored by the message driven pub/sub feature
invoicing.RegisterPublisher("CreditApproved, CreditScoring.Messages, Version=1.0.0.0", "Sales");
Using the overloads that accept a type instead of a string value is recommended. Passing types can be problematic when not using naming conventions for messages via unobtrusive mode.
Referencing event types
When an assembly containing message types is referenced, either typeof()
or a type argument may be used for type-safety when registering publishers. Sometimes it is not possible to reference an assembly containing message types. For example, the assembly may reference a different version of NServiceBus than the bridge. In these cases, the fully-qualified name of an event may be used instead. This may even be preferable to referencing message assemblies, to reduce the chance of compile-time conflicts.
Provisioning queues
By default, the bridge does not create queues for the endpoints that it proxies. This is done so that elevated privileges (which are often needed to create the queues) are not required at runtime.
The queues can be created using one of the following methods:
- Provisioning them manually using the tooling provided by the queuing system.
- Using the queue creation tooling provided by Particular Software if one exists for the transports being used. See the individual transports documentation for more details.
- Configuring the bridge to create queues of proxied endpoints automatically as described in the next section.
Automatic queue provisioning
This option requires the bridge to have administrative privileges for the queuing systems used and is not recommended for production scenarios.
Automatic queue creation for proxied endpoints is enabled by configuring the bridge as follows:
var msmq = new BridgeTransport(new MsmqTransport())
{
AutoCreateQueues = true
};
var azureServiceBus = new BridgeTransport(new AzureServiceBusTransport(connectionString))
{
AutoCreateQueues = true
};
The diagram below shows a simple MSMQ-to-AzureServiceBus configuration involving two endpoints.
and the following bridge configuration
msmq.HasEndpoint("Sales");
azureServiceBus.HasEndpoint("Billing");
When automatic queue creation is enabled a "Sales" proxy endpoint is created on the AzureServiceBus transport and a "Billing" proxy endpoint is created on the MSMQ transport. These proxy endpoints represent the endpoint on the other side of the bridge.
MSMQ | AutoCreated | AzureServiceBus | AutoCreated |
---|---|---|---|
Sales | False | Sales | True |
Billing | True | Billing | False |
The "Sales" queue on the MSMQ transport and the "Billing" queue on the AzureServiceBus transport are assumed to be created by the endpoints connected on those transport and therefore are not owned by the bridge queue creation.
Custom queue address
The bridge provides the ability to change the address of the queue of incoming messages.
When forwarding messages to MSMQ endpoints that run on different servers than the bridge, the addresses of the queues that messages should be forwarded to must be provided.
var transport = new BridgeTransport(new MsmqTransport());
transport.HasEndpoint("Finance", "finance@machinename");
var endpoint = new BridgeEndpoint("Sales", "sales@another-machine");
transport.HasEndpoint(endpoint);
Recoverability
If a message fails while it is being forwarded to the target transport, the following recoverability actions are taken:
- Three immediate retries are performed to make sure that the problem isn't transient.
- If the retries fail, the message is moved to the bridge error queue.
Error queue
The error queue used by the bridge is named bridge.
by default. Note that the default error
queue used by other platform components can not be used to enable bridging of the system-wide error queue since a bridged queue may not be used as the error queue. See the documentation around bridging platform queues for more details.
A different error queue may be configured as follows:
var msmq = new BridgeTransport(new MsmqTransport())
{
ErrorQueue = "my-msmq-bridge-error-queue"
};
var azureServiceBus = new BridgeTransport(new AzureServiceBusTransport(connectionString))
{
ErrorQueue = "my-asb-bridge-error-queue"
};
Messages moved to the error queue have the NServiceBus.
header set to allow scripted retries. Refer to the documentation for the various transports for more details on how to perform retries.
Auditing
The bridge add a NServiceBus.
header to a message while that message is transferred between transports.
The value of the header is {source-transport-name}->{target-transport-name}
. For example: msmq->sqlserver
. This header provides traceability for a message as it moves through the bridge.
Configuring transports
By default, the bridge assigns a transport name based on the type of transport being used. This means that when bridging transports of the same type, each transport must be given a unique name. For example:
var azureServiceBus1 = new BridgeTransport(new AzureServiceBusTransport(connectionStringNamepace1))
{
Name = "asb-namespace-1"
};
var azureServiceBus2 = new BridgeTransport(new AzureServiceBusTransport(connectionStringNamepace2))
{
Name = "asb-namespace-2"
};
Bridging platform queues
The bridge can be configured to allow a single ServiceControl installation to manage and monitor endpoints on all bridged transports. For example:
var transportWhereServiceControlIsInstalled = new BridgeTransport(new MsmqTransport());
transportWhereServiceControlIsInstalled.HasEndpoint("Particular.ServiceControl");
transportWhereServiceControlIsInstalled.HasEndpoint("Particular.Monitoring");
transportWhereServiceControlIsInstalled.HasEndpoint("error");
transportWhereServiceControlIsInstalled.HasEndpoint("audit");
The endpoint name used when creating a BridgeEndpoint
is case-sensitive, even if one or both transports don't require it. This is to accommodate all transports, some of which require a case-sensitive endpoint name. More details can be found on this issue.
Audit queue
Special considerations are required for the audit queue due to potentially high message volume. For example, a dedicated ServiceControl audit instance could be created for each bridged transport, to make audit ingestion more efficient.