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.
Registering multiple publishers for the same event
The messaging bridge will not allow registering multiple publishers for the same event according to the events should be published by the logical owner convention.
To allow this, use the NServiceBus.MessagingBridge version 2.1 or above and disable best practices enforcement:
bridgeConfiguration.DoNotEnforceBestPractices();
If the best practices are not enforced the bridge will log the following warning:
The following subscriptions with multiple registered publishers are ignored as best practices are not enforced:
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.
Error queue
By default, when the bridge transfers a message to the ServiceControl error queue it will attempt to translate the NServiceBus.
message header. It can only do this successfully if the address in the ReplyToAddress
header maps to some endpoint registered with the bridge. Therefore all endpoints in the system need to be registered with the bridge. Otherwise, the translation of the ReplyToAddress
header might fail for some messages, which in turn will be moved to the bridge error queue.
The translation of the ReplyToAddress
header value for failed messages can be disabled via a dedicated API setting.
bridgeConfiguration.DoNotTranslateReplyToAddressForFailedMessages();
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.
Monitoring
Heartbeats
Version 2.2 of the NServiceBus Messaging Bridge adds support for the heartbeats feature. This feature sends regular heartbeat messages from each messaging bridge transport to a ServiceControl instance. The ServiceControl instance keeps track of which endpoint instances are sending heartbeats and which ones are not. See the Hearbeats plugin documentation for more information.
Heartbeats can be configured on a per bridge transport basis. For each bridge transport that should send hearbeats, use the .
as follows:
bridgeTransport.SendHeartbeatTo(
serviceControlQueue: "ServiceControl_Queue",
frequency: TimeSpan.FromSeconds(15),
timeToLive: TimeSpan.FromSeconds(30));
ServiceControl_Queue
is a placeholder for the name of the ServiceControl input queue. The name of the ServiceControl input queue matches the ServiceControl instance name.
Heartbeat interval
Heartbeat messages are sent at a default frequency of 10 seconds. As shown above, the frequency may be overridden for each endpoint.
The frequency must be lower than the HeartbeatGracePeriod
in ServiceControl.
Time-To-Live (TTL)
Heartbeat messages are sent with a default TTL of four times the frequency. As shown above, the TTL may be overridden for each endpoint. See the documentation for expired heartbeats for more information.
Identifying scaled-out endpoints
When heartbeats are being used in a scaled-out Messaging Bridge that is using competing consumers, each bridge transport must be configured with a unique name (see Configuring transports). The bridge transport name is used in the heartbeat messages so ServiceControl can track of all instances and identify which instance sent a given heartbeat message.
Custom checks
Version 2.2 of the Messaging Bridge adds support for custom checks. The custom checks feature enables Messaging Bridge health monitoring by running custom code and reporting status (success or failure) to a ServiceControl instance. See the custom checks plugin documentation for more information. For documentation on how to write custom checks, see the Writing Custom Checks document
The custom checks feature can be enabled per bridge transport by using ReportCustomChecksTo
as follow:
bridgeTransport.ReportCustomChecksTo(
serviceControlQueue: "ServiceControl_Queue",
timeToLive: TimeSpan.FromSeconds(30));
Each custom check is executed once, and the result is sent to each bridge transport configured to report custom checks. When configured to bridge platform queues, it is only necessary to report custom checks on one of the bridge transports. Otherwise, a custom check result will be reported multiple times to the same platform instance.
Time-To-Live (TTL)
Custom check results are sent with a default TTL of four times the interval for periodic checks or infinite for one-time checks. As shown above, the TTL may be overridden for each bridge transport.