Add builder pattern API for multiple senders/processors in Aspire.Azure.Messaging.ServiceBus
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
## Is there an existing issue for this?
- [x] I have searched the existing issues
## Is your feature request related to a problem? Please describe the problem.
Currently, if you want to work with multiple Service Bus queues/topics from the same namespace, you must manually create and register senders and processors:
```csharp
// Current approach - manual registration
builder.AddAzureServiceBusClient("messaging");
builder.Services.AddSingleton(sp =>
{
var client = sp.GetRequiredService();
return client.CreateSender("queue1");
});
builder.Services.AddSingleton(sp =>
{
var client = sp.GetRequiredService();
return client.CreateSender("queue2");
});
builder.Services.AddSingleton(sp =>
{
var client = sp.GetRequiredService();
return client.CreateProcessor("queue3");
});
// Can't easily inject specific senders/processors by name
// All senders are of type ServiceBusSender, no way to distinguish in DI
```
The Cosmos DB integration already solves this problem with `AddAzureCosmosDatabase()` and `CosmosDatabaseBuilder`, and blob storage has a similar proposal in #12179, but Service Bus lacks an equivalent pattern.
## Describe the solution you'd like
Add a **builder pattern API** similar to `CosmosDatabaseBuilder` that allows registering multiple senders/processors against a single `ServiceBusClient`:
```csharp
// Proposed API
builder.AddAzureServiceBus("messaging")
.AddKeyedSender("orders")
.AddKeyedSender("notifications")
.AddKeyedProcessor("commands", options =>
{
options.MaxConcurrentCalls = 5;
options.AutoCompleteMessages = true;
})
.AddKeyedTopicSender("events")
.AddKeyedSubscriptionProcessor("events", "subscription1");
// Usage in services
public class OrderService(
[FromKeyedServices("orders")] ServiceBusSender orderSender,
[FromKeyedServices("notifications")] ServiceBusSender notificationSender)
{
// All senders share the same underlying ServiceBusClient
public async Task PlaceOrder(Order order)
{
await orderSender.SendMessageAsync(new ServiceBusMessage(JsonSerializer.Serialize(order)));
await notificationSender.SendMessageAsync(new ServiceBusMessage("Order placed"));
}
}
public class CommandProcessor(
[FromKeyedServices("commands")] ServiceBusProcessor commandProcessor) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
commandProcessor.ProcessMessageAsync += HandleMessage;
commandProcessor.ProcessErrorAsync += HandleError;
await commandProcessor.StartProcessingAsync(stoppingToken);
}
}
```
### Benefits
1. **Resource Efficiency**: Single `ServiceBusClient` instance shared across all senders/processors
2. **Better DI Integration**: Senders and processors registered as keyed services, easily injectable
3. **Cleaner Code**: Fluent builder API for declaring multiple queues/topics
4. **Type Safety**: Keyed services allow injecting specific senders/processors by name
5. **Consistency**: Matches the pattern established by Cosmos DB and proposed for Blob Storage (#12179)
6. **Reduced Boilerplate**: No manual lambda registrations needed
### Implementation Requirements
1. **New `ServiceBusBuilder` class** (similar to `CosmosDatabaseBuilder`):
- Constructor accepting `IHostApplicationBuilder`, connection name, settings, client options
- `AddClient()` / `AddKeyedClient()` internal methods to register the `ServiceBusClient`
- `AddKeyedSender(string queueOrTopicName)` - Register a `ServiceBusSender` as keyed service
- `AddKeyedProcessor(string queueName, Action?)` - Register a `ServiceBusProcessor` as keyed service
- `AddKeyedTopicSender(string topicName)` - Register a sender for a specific topic
- `AddKeyedSubscriptionProcessor(string topicName, string subscriptionName, Action?)` - Register a processor for topic subscription
- `AddKeyedReceiver(string queueOrSubscriptionName, Action?)` - Register a `ServiceBusReceiver` as keyed service
- `AddKeyedSubscriptionReceiver(string topicName, string subscriptionName, Action?)` - Register a subscription receiver
2. **New extension methods** in `AspireServiceBusExtensions`:
- `AddAzureServiceBus()` - Returns `ServiceBusBuilder`
- `AddKeyedAzureServiceBus()` - Returns `ServiceBusBuilder` with keyed client
3. **Playground sample enhancement**:
- Update `ServiceBusWorker` to use new builder pattern
- Demonstrate multiple queues/topics
- Show keyed service injection for senders and processors
- Example with both queue and topic/subscription patterns
### API Consistency
**Cosmos DB (existing):**
```csharp
builder.AddAzureCosmosDatabase("db")
.AddKeyedContainer("entries")
.AddKeyedContainer("users");
```
**Blob Storage (proposed - #12179):**
```csharp
builder.AddAzureBlobService("storage")
.AddKeyedContainer("images")
.AddKeyedContainer("documents");
```
**Service Bus (this proposal):**
```csharp
builder.AddAzureServiceBus("messaging")
.AddKeyedSender("orders")
.AddKeyedProcessor("commands")
.AddKeyedSubscriptionProcessor("events", "subscription1");
```
### Example Usage Scenarios
#### Scenario 1: Multiple Queue Senders
```csharp
builder.AddAzureServiceBus("messaging")
.AddKeyedSender("orders")
.AddKeyedSender("payments")
.AddKeyedSender("notifications");
```
#### Scenario 2: Sender + Processor for Same Queue
```csharp
builder.AddAzureServiceBus("messaging")
.AddKeyedSender("commands")
.AddKeyedProcessor("commands", options =>
{
options.MaxConcurrentCalls = 10;
options.AutoCompleteMessages = false; // Manual completion
});
```
#### Scenario 3: Topic/Subscription Pattern
```csharp
builder.AddAzureServiceBus("messaging")
.AddKeyedTopicSender("order-events")
.AddKeyedSubscriptionProcessor("order-events", "email-service")
.AddKeyedSubscriptionProcessor("order-events", "inventory-service");
```
#### Scenario 4: Mixed Queues and Topics
```csharp
builder.AddAzureServiceBus("messaging")
.AddKeyedSender("order-queue")
.AddKeyedProcessor("order-queue")
.AddKeyedTopicSender("notifications")
.AddKeyedSubscriptionProcessor("notifications", "sms-sub")
.AddKeyedSubscriptionProcessor("notifications", "email-sub");
```
## Additional context
This feature would bring the Service Bus integration to feature parity with the Cosmos DB integration and aligns with the proposed Blob Storage enhancement (#12179). It provides a consistent API pattern across all Azure integrations in Aspire.
**Additional Considerations:**
- **Processor Lifecycle**: Processors need to be started/stopped. The builder should register them, but the user is responsible for lifecycle management (typically in a `BackgroundService`)
- **Session Support**: Future enhancement could add `AddKeyedSessionProcessor()` for session-enabled queues
- **Sub-queue Support**: Could support dead-letter queues or other sub-queues via options parameter
Reference implementation:
- `src/Components/Aspire.Microsoft.Azure.Cosmos/CosmosDatabaseBuilder.cs`
- `src/Components/Aspire.Microsoft.Azure.Cosmos/AspireMicrosoftAzureCosmosExtensions.cs`
- `playground/AzureServiceBus/ServiceBusWorker/Program.cs`
Related issue: #12179 (Blob Storage builder pattern)
Contributor guide
Assessment
This issue has not been assessed yet.