Skip to content

Commit 9a02c39

Browse files
committed
Docs for multi-tenancy usage with Azure Service Bus
1 parent 1419085 commit 9a02c39

File tree

6 files changed

+171
-3
lines changed

6 files changed

+171
-3
lines changed

docs/.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ const config: UserConfig<DefaultTheme.Config> = {
139139
{text: 'Topics and Subscriptions', link:'/guide/messaging/transports/azureservicebus/topics'},
140140
{text: 'Interoperability', link:'/guide/messaging/transports/azureservicebus/interoperability'},
141141
{text: 'Session Identifiers and FIFO Queues', link: '/guide/messaging/transports/azureservicebus/session-identifiers'},
142-
{text: 'Scheduled Delivery', link: '/guide/messaging/transports/azureservicebus/scheduled'}
142+
{text: 'Scheduled Delivery', link: '/guide/messaging/transports/azureservicebus/scheduled'},
143+
{text: 'Multi-Tenancy', link: '/guide/messaging/transports/azureservicebus/multi-tenancy'}
143144
]},
144145
{text: 'Amazon SQS', link: '/guide/messaging/transports/sqs/', items:[
145146
{text: 'Publishing', link:'/guide/messaging/transports/sqs/publishing'},
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Multi-Tenancy with Azure Service Bus <Badge type="tip" text="3.4" />
2+
3+
Let's take a trip to the world of IoT where you might very well build a single cloud hosted service that needs
4+
to communicate via Rabbit MQ with devices at your customers sites. You'd preferably like to keep traffic separate
5+
so that one customer never accidentally receives information from another customer. In this case, Wolverine now
6+
lets you register separate Rabbit MQ brokers -- or at least separate virtual hosts within a single Rabbit MQ broker --
7+
for each tenant.
8+
9+
::: info
10+
Definitely see [Multi-Tenancy with Wolverine](/guide/handlers/multi-tenancy) for more information about how
11+
Wolverine tracks the tenant id across messages.
12+
:::
13+
14+
Let's just jump straight into a simple example of the configuration:
15+
16+
snippet: sample_configuring_azure_service_bus_for_multi_tenancy
17+
18+
::: warning
19+
Wolverine has no way of creating new Azure Service Bus namespaces for you
20+
:::
21+
22+
In the code sample above, I'm setting up the Azure Service Bus transport to "know" that there are multiple tenants
23+
with separate Azure Service Bus fully qualified namespaces.
24+
25+
::: tip
26+
Note that Wolverine uses the credentials specified for the default Azure Service
27+
Bus connection for all tenant specific connections
28+
:::
29+
30+
At runtime, if we send a message like so:
31+
32+
<!-- snippet: sample_send_message_to_specific_tenant -->
33+
<a id='snippet-sample_send_message_to_specific_tenant'></a>
34+
```cs
35+
public static async Task send_message_to_specific_tenant(IMessageBus bus)
36+
{
37+
// Send a message tagged to a specific tenant id
38+
await bus.PublishAsync(new Message1(), new DeliveryOptions { TenantId = "two" });
39+
}
40+
```
41+
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/multi_tenancy_through_virtual_hosts.cs#L211-L219' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_send_message_to_specific_tenant' title='Start of snippet'>anchor</a></sup>
42+
<!-- endSnippet -->
43+
44+
In the case above, in the Wolverine internals, it:
45+
46+
1. Routes the message to a Azure Service Bus queue named "outgoing"
47+
2. Within the sender for that queue, Wolverine sees that `TenantId == "two"`, so it sends the message to the "outgoing" queue
48+
on the Azure Service Bus connection that we specified for the "two" tenant id.
49+
50+
Likewise, see the listening set up against the "incoming" queue above. At runtime, this Wolverine application will be
51+
listening to a queue named "incoming" on the default Azure Service Bus namespace and a separate queue named "incoming" on the separate
52+
fully qualified namespaces for the known tenants. When a message is received at any of these queues, it's tagged with the
53+
`TenantId` that's appropriate for each separate tenant-specific listening endpoint. That helps Wolverine also track
54+
tenant specific operations (with Marten maybe?) and tracks the tenant id across any outgoing messages or responses as well.
55+
56+
57+
58+
59+

src/Transports/Azure/Wolverine.AzureServiceBus.Tests/Samples.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using Azure.Messaging.ServiceBus;
22
using JasperFx.Core;
3+
using Microsoft.Extensions.Configuration;
34
using Microsoft.Extensions.Hosting;
45
using Oakton.Resources;
56
using Wolverine.ComplianceTests.Compliance;
7+
using Wolverine.Transports.Sending;
68
using Wolverine.Util;
9+
using Xunit;
710

811
namespace Wolverine.AzureServiceBus.Tests;
912

@@ -115,6 +118,74 @@ public static async Task configure_custom_mappers()
115118

116119
#endregion
117120
}
121+
122+
public class multi_tenanted_brokers
123+
{
124+
[Fact]
125+
public void show_bootstrapping()
126+
{
127+
#region sample_configuring_azure_service_bus_for_multi_tenancy
128+
129+
var builder = Host.CreateApplicationBuilder();
130+
131+
builder.UseWolverine(opts =>
132+
{
133+
// One way or another, you're probably pulling the Azure Service Bus
134+
// connection string out of configuration
135+
var azureServiceBusConnectionString = builder
136+
.Configuration
137+
.GetConnectionString("azure-service-bus");
138+
139+
// Connect to the broker in the simplest possible way
140+
opts.UseAzureServiceBus(azureServiceBusConnectionString)
141+
142+
// This is the default, if there is no tenant id on an outgoing message,
143+
// use the default broker
144+
.TenantIdBehavior(TenantedIdBehavior.FallbackToDefault)
145+
146+
// Or tell Wolverine instead to just quietly ignore messages sent
147+
// to unrecognized tenant ids
148+
.TenantIdBehavior(TenantedIdBehavior.IgnoreUnknownTenants)
149+
150+
// Or be draconian and make Wolverine assert and throw an exception
151+
// if an outgoing message does not have a tenant id
152+
.TenantIdBehavior(TenantedIdBehavior.TenantIdRequired)
153+
154+
// Add new tenants by registering the tenant id and a separate fully qualified namespace
155+
// to a different Azure Service Bus connection
156+
.AddTenantByNamespace("one", builder.Configuration.GetValue<string>("asb_ns_one"))
157+
.AddTenantByNamespace("two", builder.Configuration.GetValue<string>("asb_ns_two"))
158+
.AddTenantByNamespace("three", builder.Configuration.GetValue<string>("asb_ns_three"))
159+
160+
// OR, instead, add tenants by registering the tenant id and a separate connection string
161+
// to a different Azure Service Bus connection
162+
.AddTenantByConnectionString("four", builder.Configuration.GetConnectionString("asb_four"))
163+
.AddTenantByConnectionString("five", builder.Configuration.GetConnectionString("asb_five"))
164+
.AddTenantByConnectionString("six", builder.Configuration.GetConnectionString("asb_six"));
165+
166+
// This Wolverine application would be listening to a queue
167+
// named "incoming" on all Azure Service Bus connections, including the default
168+
opts.ListenToAzureServiceBusQueue("incoming");
169+
170+
// This Wolverine application would listen to a single queue
171+
// at the default connection regardless of tenant
172+
opts.ListenToAzureServiceBusQueue("incoming_global")
173+
.GlobalListener();
174+
175+
// Likewise, you can override the queue, subscription, and topic behavior
176+
// to be "global" for all tenants with this syntax:
177+
opts.PublishMessage<Message1>()
178+
.ToAzureServiceBusQueue("message1")
179+
.GlobalSender();
180+
181+
opts.PublishMessage<Message2>()
182+
.ToAzureServiceBusTopic("message2")
183+
.GlobalSender();
184+
});
185+
186+
#endregion
187+
}
188+
}
118189
}
119190

120191
#region sample_custom_azure_service_bus_mapper

src/Transports/Azure/Wolverine.AzureServiceBus/AzureServiceBusConfiguration.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Wolverine.AzureServiceBus.Internal;
44
using Wolverine.Configuration;
55
using Wolverine.Transports;
6+
using Wolverine.Transports.Sending;
67

78
namespace Wolverine.AzureServiceBus;
89

@@ -26,6 +27,43 @@ protected override AzureServiceBusQueueSubscriberConfiguration createSubscriberE
2627
{
2728
return new AzureServiceBusQueueSubscriberConfiguration(subscriberEndpoint);
2829
}
30+
31+
/// <summary>
32+
/// Override the sending logic behavior for unknown or missing tenant ids when
33+
/// using multi-tenanted namespaces
34+
/// </summary>
35+
/// <param name="tenantedIdBehavior"></param>
36+
/// <returns></returns>
37+
public AzureServiceBusConfiguration TenantIdBehavior(TenantedIdBehavior tenantedIdBehavior)
38+
{
39+
Transport.TenantedIdBehavior = tenantedIdBehavior;
40+
return this;
41+
}
42+
43+
/// <summary>
44+
/// Add a connection to a different Azure Service Bus broker for the named tenant using a fully
45+
/// qualified namespace
46+
/// </summary>
47+
/// <param name="tenantId"></param>
48+
/// <param name="fullyQualifiedNamespace"></param>
49+
/// <returns></returns>
50+
public AzureServiceBusConfiguration AddTenantByNamespace(string tenantId, string fullyQualifiedNamespace)
51+
{
52+
Transport.Tenants[tenantId].Transport.FullyQualifiedNamespace = fullyQualifiedNamespace;
53+
return this;
54+
}
55+
56+
/// <summary>
57+
/// Add a connection to a different Azure Service Bus broker for the named tenant using a connection string
58+
/// </summary>
59+
/// <param name="tenantId"></param>
60+
/// <param name="connectionString"></param>
61+
/// <returns></returns>
62+
public AzureServiceBusConfiguration AddTenantByConnectionString(string tenantId, string connectionString)
63+
{
64+
Transport.Tenants[tenantId].Transport.ConnectionString = connectionString;
65+
return this;
66+
}
2967

3068
/// <summary>
3169
/// Add explicit configuration to an AzureServiceBus queue that is being created by

src/Transports/RabbitMQ/Wolverine.RabbitMQ.Tests/multi_tenancy_through_virtual_hosts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ public static async Task Configure()
303303

304304
// More on this in the docs....
305305
opts.PublishMessage<Message1>()
306-
.ToRabbitQueue("outgoing");
306+
.ToRabbitQueue("outgoing").GlobalSender();
307307
});
308308

309309
#endregion

src/Transports/RabbitMQ/Wolverine.RabbitMQ/Internal/RabbitMqTransportExpression.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ public RabbitMqTransportExpression TenantIdBehavior(TenantedIdBehavior tenantedI
4242
/// <param name="tenantId"></param>
4343
/// <param name="virtualHostName"></param>
4444
/// <returns></returns>
45-
/// <exception cref="NotImplementedException"></exception>
4645
public RabbitMqTransportExpression AddTenant(string tenantId, string virtualHostName)
4746
{
4847
Transport.Tenants[tenantId] = new RabbitMqTenant(tenantId, virtualHostName);

0 commit comments

Comments
 (0)