﻿# Bridge configuration options


## Hosting

NServiceBus Messaging Bridge is hosted using the [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host), which takes care of lifecycle management, configuration, logging, and other concerns.

<!-- snippet: generic-host -->

```cs
await Host.CreateDefaultBuilder()
    .UseNServiceBusBridge(bridgeConfiguration =>
    {
        // Configure the bridge
    })
    .Build()
    .RunAsync();
```

<!-- endsnippet -->

The overload that accepts a [`HostBuilderContext`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuildercontext) provides access to the [`IConfiguration` type](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration) and other host-related details.

<!-- snippet: generic-host-builder-context -->

```cs
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, TopicTopology.Default))
        {
            Concurrency = concurrency
        };

        bridgeConfiguration.AddTransport(transport);

        // more configuration...
    })
    .Build()
    .RunAsync();
```

<!-- endsnippet -->

## Critical error handling

A bridge can encounter a [critical error](/nservicebus/hosting/critical-errors.md) when a transport cannot recover from an infrastructure failure. Starting with version 5.1, `DefineCriticalErrorAction` can be used to stop the affected bridge endpoint and terminate the process:

<!-- snippet: bridge-critical-error-action -->

```cs
bridgeConfiguration.DefineCriticalErrorAction(async (context, cancellationToken) =>
{
    var fatalMessage =
        $"The following critical error was encountered:{Environment.NewLine}" +
        $"{context.Error}{Environment.NewLine}" +
        "The bridge is shutting down.";

    try
    {
        await context.Stop(cancellationToken);
    }
    finally
    {
        Environment.FailFast(fatalMessage, context.Exception);
    }
});
```

<!-- endsnippet -->

Configure the hosting environment to [restart the terminated process](/nservicebus/hosting/critical-errors.md#how-do-i-deal-with-persistent-critical-errors-terminate-and-restart-the-process). Calling `context.Stop` without terminating the process stops only the bridge endpoint that raised the critical error.


## 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.

<!-- snippet: endpoint-registration -->

```cs
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, TopicTopology.Default));
        asb.HasEndpoint("Finance.Invoicing");
        asb.HasEndpoint("Finance.Billing");

        bridgeConfiguration.AddTransport(msmq);
        bridgeConfiguration.AddTransport(asb);
    })
    .Build()
    .RunAsync();
```

<!-- endsnippet -->

> [!NOTE]
> 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 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](#recoverability-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 bridges. 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.

<!-- snippet: register-publisher -->

```cs
var msmq = new BridgeTransport(new MsmqTransport());
msmq.HasEndpoint("Sales");
msmq.HasEndpoint("Finance.Billing");

var asb = new BridgeTransport(new AzureServiceBusTransport(connectionString, TopicTopology.Default));
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);
```

<!-- endsnippet -->

> [!WARNING]
> 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](https://github.com/Particular/NServiceBus.MessagingBridge/issues/175).

Legacy transport versions that use [message-driven pub/sub](/nservicebus/messaging/publish-subscribe/index.md#mechanics-message-driven-persistence-based) 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.

<!-- snippet: register-publisher-legacy -->

```cs
// 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");
```

<!-- endsnippet -->

> [!NOTE]
> 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](/nservicebus/messaging/conventions.md).

## 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](/nservicebus/messaging/messages-events-commands.md) convention.

To allow this, use [NServiceBus.MessagingBridge version 2.1 or above](configuration.md#registering-multiple-publishers-for-the-same-event) and disable best practices enforcement:

<!-- snippet: do-not-enforce-best-practices -->

```cs
bridgeConfiguration.DoNotEnforceBestPractices();
```

<!-- endsnippet -->

If 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](/transports/index.md) for more details.
- Configuring the bridge to create queues of [proxied endpoints](/nservicebus/bridge/configuration.md#registering-endpoints) automatically as described in the next section.

### Automatic queue provisioning

> [!NOTE]
> 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:

<!-- snippet: auto-create-queues -->

```cs
var msmq = new BridgeTransport(new MsmqTransport())
{
    AutoCreateQueues = true
};

var azureServiceBus = new BridgeTransport(new AzureServiceBusTransport(connectionString, TopicTopology.Default))
{
    AutoCreateQueues = true
};
```

<!-- endsnippet -->

The diagram below shows a simple MSMQ-to-AzureServiceBus configuration involving two endpoints.

```mermaid
flowchart LR

Br(Bridge)
Sales[Endpoint Sales] <---> Br
Br <---> Billing[Endpoint Billing]
subgraph MSMQ
  Sales
end
subgraph AzureServiceBus
  Billing
end
```

and the following bridge configuration

<!-- snippet: auto-create-queues-proxies -->

```cs
msmq.HasEndpoint("Sales");
azureServiceBus.HasEndpoint("Billing");
```

<!-- endsnippet -->

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 to those transports and are not created by the bridge.

## Custom queue address

The bridge provides the ability to change the address of the incoming message queue.

> [!NOTE]
> 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.

<!-- snippet: custom-address -->

```cs
var transport = new BridgeTransport(new MsmqTransport());
transport.HasEndpoint("Finance", "finance@machinename");

var endpoint = new BridgeEndpoint("Sales", "sales@another-machine");
transport.HasEndpoint(endpoint);
```

<!-- endsnippet -->

## Recoverability

If a message fails while it is being forwarded to the target transport, the following recoverability actions are taken:

1. Three immediate retries are performed to make sure that the problem isn't transient.
1. 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.error` by default. Note that the default `error` queue used by other platform components cannot 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](#bridging-platform-queues) for more details.

A different error queue may be configured as follows:

<!-- snippet: custom-error-queue -->

```cs
var msmq = new BridgeTransport(new MsmqTransport())
{
    ErrorQueue = "my-msmq-bridge-error-queue"
};

var azureServiceBus = new BridgeTransport(new AzureServiceBusTransport(connectionString, TopicTopology.Default))
{
    ErrorQueue = "my-asb-bridge-error-queue"
};
```

<!-- endsnippet -->

Messages moved to the error queue have the [`NServiceBus.FailedQ`](/nservicebus/messaging/headers.md#error-forwarding-headers-nservicebus-failedq) header set to allow scripted retries. Refer to the documentation for the [various transports](/transports/index.md) for more details on how to perform retries.

## Auditing

The bridge adds a `NServiceBus.Bridge.Transfer` 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:

<!-- snippet: custom-transport-name -->

```cs
var azureServiceBus1 = new BridgeTransport(new AzureServiceBusTransport(connectionStringNamepace1, TopicTopology.Default))
{
    Name = "asb-namespace-1"
};

var azureServiceBus2 = new BridgeTransport(new AzureServiceBusTransport(connectionStringNamepace2, TopicTopology.Default))
{
    Name = "asb-namespace-2"
};
```

<!-- endsnippet -->

## Bridging platform queues

The bridge can be configured to allow a [single ServiceControl installation to manage and monitor endpoints on all bridged transports](/servicecontrol/servicecontrol-instances/remotes.md#overview-multi-transport-deployments). For example:

<!-- snippet: platform-bridging -->

```cs
var transportWhereServiceControlIsInstalled = new BridgeTransport(new MsmqTransport());

transportWhereServiceControlIsInstalled.HasEndpoint("Particular.ServiceControl");
transportWhereServiceControlIsInstalled.HasEndpoint("Particular.Monitoring");
transportWhereServiceControlIsInstalled.HasEndpoint("error");
transportWhereServiceControlIsInstalled.HasEndpoint("audit");
```

<!-- endsnippet -->

> [!WARNING]
> 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](https://github.com/Particular/NServiceBus.MessagingBridge/issues/175).

### Error queue

By default, when the bridge transfers a message to the ServiceControl error queue it will attempt to translate the [`NServiceBus.ReplyToAddress`](/nservicebus/messaging/headers.md#messaging-interaction-headers-nservicebus-replytoaddress) 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](/nservicebus/bridge/configuration.md#recoverability-error-queue).

The translation of the `ReplyToAddress` header value for failed messages can be disabled via a dedicated API setting.

<!-- snippet: do-not-translate-reply-to-address-for-failed-messages -->

```cs
bridgeConfiguration.DoNotTranslateReplyToAddressForFailedMessages();
```

<!-- endsnippet -->

> [!NOTE]
> Not translating the `NServiceBus.ReplyToAddress` message header for failed messages can cause bridge shovel errors, especially when bridging the SqlServer and MSMQ transports.



### Audit queue

Special considerations are required for the audit queue due to potentially high message volume. For example, a [dedicated ServiceControl audit instance](/servicecontrol/audit-instances/index.md) 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 [Heartbeats plugin documentation](/monitoring/heartbeats/index.md) for more information.

Heartbeats can be configured on a per bridge transport basis. For each bridge transport that should send heartbeats, use `.SendHeartbeatTo` as follows:

<!-- snippet: configure-heartbeats -->

```cs
bridgeTransport.SendHeartbeatTo(
    serviceControlQueue: "ServiceControl_Queue",
    frequency: TimeSpan.FromSeconds(15),
    timeToLive: TimeSpan.FromSeconds(30));
```

<!-- endsnippet -->

> [!NOTE]
> `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](/servicecontrol/servicecontrol-instances/configuration.md#host-settings-servicecontrolinstancename).

#### Heartbeat interval

Heartbeat messages are sent at a default frequency of 10 seconds. As shown above, the frequency may be overridden for each endpoint.

> [!NOTE]
> The frequency must be lower than the [`HeartbeatGracePeriod`](/servicecontrol/servicecontrol-instances/configuration.md#plugin-specific-servicecontrolheartbeatgraceperiod) 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](/monitoring/heartbeats/expired-heartbeats.md) for more information.

#### Identifying scaled-out endpoints

When heartbeats are being used in a [scaled-out Messaging Bridge that is using competing consumers](/nservicebus/bridge/performance.md#scaling-out-competing-consumers), each bridge transport must be configured with a unique name (see [Configuring transports](#auditing-configuring-transports)). The bridge transport name is used in the heartbeat messages so ServiceControl can track 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](/monitoring/custom-checks/index.md) for more information. For documentation on how to write custom checks, see [Writing custom checks](/monitoring/custom-checks/writing-custom-checks.md).

The custom checks feature can be enabled per bridge transport by using `ReportCustomChecksTo` as follows:

<!-- snippet: configure-custom-checks -->

```cs
bridgeTransport.ReportCustomChecksTo(
    serviceControlQueue: "ServiceControl_Queue",
    timeToLive: TimeSpan.FromSeconds(30));
```

<!-- endsnippet -->

> [!NOTE]
> 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](#bridging-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.




