This is part of the NServiceBus Upgrade Guide from Version 8 to 9, which also includes the following individual upgrade guides for specific components:
Transports
- AmazonSQS Transport Upgrade Version 6 to 7
- Azure Service Bus Transport Upgrade Version 3 to 4
- Azure Service Bus Transport Upgrade Version 4 to 5
- SQL Server Transport Upgrade Version 7 to 8
Hosting
Other
Upgrading from Azure Service Bus transport version 4 to version 5 is a major upgrade and requires careful planning. Read the entire upgrade guide before beginning the upgrade process.
Version 5 of the transport introduces the concept of choosing a topic topology. The following two topologies are supported:
- Migration topology
- Topic-per-event type topology
The topology selection must be explicitly passed into the constructor of the transport when the transport is being created.
Topologies
Topic-per-event type topology
This topology is the default and preferred choice for new endpoints that do not require backward compatibility with previous versions of the transport. It optimizes event routing, reduces filter overhead, aligns with industry best practices, and improves observability in the event routing path.
The topology represents each event as its own topic. Subscribers add their subscription with forwarding enabled under the topic of the events they are interested in. These subscriptions do not require any filtering rules.
Least-privilege
Subscribing and unsubscribing to events requires management rights to the Azure Service Bus namespace because subscriptions need to be created or deleted. It is possible to run the transport with least-privilege access by deploying the necessary subscriptions as part of the endpoint deployment. This can be done by briefly enabling installers, using the provided tool, or utilizing infrastructure-as-code tools such as Bicep, Terraform, or Pulumi.
Migration topology
The migration topology is a hybrid design that allows transitioning from the previously used topology to the topic-per-event-type topology on an event-by-event basis, avoiding the need for a big-bang migration process.
The migration topology should be used by endpoints that require backward compatibility with endpoints using the previous topology.
In this topology, each event type must be explicitly mapped as either "to be migrated" or "migrated". Events yet to be migrated are published or subscribed in the backward-compatible way, while migrated events follow the topic-per-event-type topology.
Least-privilege
Subscribing and unsubscribing to a "to be migrated" event at runtime is supported even when connected endpoints do not have management rights to the Azure Service Bus namespace. This ensures that the migration topology remains backward-compatible from a privilege mode perspective.
For migrated events, subscribing and unsubscribing requires management rights since subscriptions need to be created or deleted. It is possible to run the transport with least-privilege access by deploying the necessary subscriptions during the endpoint deployment using the provided tool or infrastructure-as-code tools such as Bicep, Terraform, or Pulumi.
Migrating existing endpoints
While it is possible to migrate events individually, it is currently not supported to partially migrate a delivery path of a single event. This means that when an event is migrated, both the publisher and all the subscribers must migrate in one step. If a partial migration approach is necessary, be sure to reach out to support.
The following endpoint configuration snippets demonstrate how a migration could take place, assuming the following scenario:
Publisher1
publishesEvent1
which is subscribed bySubscriber1
andSubscriber2
Publisher1
also publishesEvent2
which is subscribed bySubscriber1
andSubscriber3
Publisher2
publishesEvent3
which is subscribed bySubscriber3
andSubscriber4
To use the migration topology, both publishers and subscribers must be on NServiceBus 9 or higher. It is not required to upgrade every endpoint to the new version of the transport as long as events are correctly mapped to be published or subscribed in a backward-compatible way where necessary.
For example, if Subscriber4
cannot be upgraded to a newer version of NServiceBus and the transport, Publisher2
can either:
- Stay on the older version of the transport.
- Upgrade but explicitly mark
Event3
to be published in a backward-compatible way:
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
// Publishes and/or subscribes using the “old” single-topic (here bundle-1) approach.
topology.EventToMigrate<Event3>();
If Subscriber3
is upgraded to the new version of the transport, it must map Event2
and Event3
, while Event3
may need to remain marked as "to be migrated" until Subscriber4
can be upgraded:
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
topology.EventToMigrate<Event2>();
topology.EventToMigrate<Event3>();
and the Publisher1
configuration
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
topology.EventToMigrate<Event1>();
topology.EventToMigrate<Event2>();
assuming Subscriber1
and Subscriber2
can be migrated the Publisher1
configuration could be switched to
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
// Publishes this event using the new “topic per event” approach (here to a Namespace.Event1 topic).
topology.MigratedPublishedEvent<Event1>();
topology.EventToMigrate<Event2>();
the Subscriber1
configuration
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
topology.MigratedSubscribedEvent<Event1>();
topology.EventToMigrate<Event2>();
and the Subscriber2
configuration
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
// Subscribes to this event using the new “topic per event” approach (here to a Namespace.Event1 topic)
topology.MigratedSubscribedEvent<Event1>();
or directly using the topic per event type topology since it only ever subscribes to Event1
which is only published in the new way.
var topology = TopicTopology.Default;
The Subscriber3
configuration would for a period of time look like
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
topology.EventToMigrate<Event2>();
topology.EventToMigrate<Event3>();
until Publisher1
switches Event2
to be published in the new way
var topology = TopicTopology.MigrateFromSingleDefaultTopic();
topology.MigratedPublishedEvent<Event1>();
topology.MigratedPublishedEvent<Event2>();
or directly using the topic per event type topology since it only ever publishes to Event1 and Event2 now in the new way.
var topology = TopicTopology.Default;
Order of migration
Generally, it does not matter whether the publisher or the subscriber is upgraded first, as long as the migration topology settings align with the subscribers' requirements. If a publisher is upgraded before all subscribers, it must be configured to publish events in a backward-compatible way. If the subscribers are upgraded first, they must subscribe to events in a backward-compatible way.
Switching the event delivery path to the new topic-per-event-type approach is a two-step process.
First, ensure that the infrastructure for the event delivery (topic and all subscriptions) is created. This can be done in a number of ways:
- If endpoints have installers enabled, the subscribers can be restarted after the event is marked as "migrated" in the topology configuration. This means that during the startup process the necessary infrastructure for the event is created. The old infrastructure (subscription on the common topic) still exists and is being used to deliver the events
- Using the provided tool
- Using infrastructure-as-code tools such as Bicep, Terraform, or Pulumi
The second step is to mark the event as "migrated" in the publisher configuration and re-deploy the endpoint.
To reduce CPU and memory overhead, subscriber endpoints should disable the AutoSubscribe feature for the specific event to prevent unnecessary old subscriptions or the deletion of no-longer-used filter rules.
All endpoints using TopicTopology.
can be considered fully migrated.
Cleanup of no longer used entities on Azure Service Bus
If the migration takes a long time, it may be desirable to delete old subscriptions or rules that are no longer needed to reduce CPU and memory overhead on the single topic still used by some endpoints.
Once all events have been migrated, the old single topic can be deleted.
Migrating from non-default topics or hierarchies
Use either TopicTopology.
or TopicTopology.
.
The default topic name is bundle-1
. In case that one is used create the migration topology with TopicTopology.
.
Migrating subscription name customizations
Previous versions of the transport allowed mapping from queue names to subscription names using function delegates. While flexible, this approach made it difficult to store logic in application configuration.
Starting with v5 of the transport, subscription names can be assigned directly:
topology.OverrideSubscriptionNameFor("QueueName", "SubscriptionName")
For more advanced scenarios, mappings can be stored in configuration:
{
...
"QueueNameToSubscriptionNameMap": {
"QueueName": "SubscriptionName"
}
}
The assumption is that any previous delegate invocation would needed to be idempotent to create reliable runtime behavior. Subscription names must adhere to the limits outlined in the Microsoft documentation on subscription creation and are automatically validated during startup.
Migrating rule name customizations
Previously, rule names could be assigned using function delegates. Starting with v5, rule names can be mapped directly:
topology.EventToMigrate<TEventType>("MyRuleName")
Or via configuration:
{
"$type": "migration-topology-options",
...
"EventsToMigrateMap": [
"Namespace.Event1"
],
"SubscribedEventToRuleNameMap": {
"Namespace.Event1": "Event1Rule"
}
}
The assumption is that any previous delegate invocation would needed to be idempotent to create reliable runtime behavior. Rules names must adhere to the limits outlined in the Microsoft documentation on subscription creation and are automatically validated during startup.