# Integration Standard: Azure Service Bus

## Patterns

### Queue vs Topic
- **Queue**: Point-to-point, one consumer per message. Use for jobs, commands.
- **Topic + Subscription**: Fan-out, multiple consumers. Use for domain events.

### Sender Pattern
```csharp
public class ServiceBusSender<T>
{
    private readonly ServiceBusSender _sender;

    public async Task SendAsync(T message, CancellationToken ct = default)
    {
        var json = JsonSerializer.Serialize(message);
        var serviceBusMessage = new ServiceBusMessage(json)
        {
            ContentType = "application/json",
            MessageId = Guid.NewGuid().ToString(),
            Subject = typeof(T).Name // For filtering
        };
        await _sender.SendMessageAsync(serviceBusMessage, ct);
    }
}
```

### Receiver Pattern (with IHostedService)
```csharp
public class OrderProcessor : IHostedService
{
    private ServiceBusProcessor _processor;

    public async Task StartAsync(CancellationToken ct)
    {
        _processor.ProcessMessageAsync += HandleMessageAsync;
        _processor.ProcessErrorAsync += HandleErrorAsync;
        await _processor.StartProcessingAsync(ct);
    }

    private async Task HandleMessageAsync(ProcessMessageEventArgs args)
    {
        var order = JsonSerializer.Deserialize<OrderCreatedEvent>(args.Message.Body);
        await _orderService.ProcessAsync(order!);
        await args.CompleteMessageAsync(args.Message);
    }

    private Task HandleErrorAsync(ProcessErrorEventArgs args)
    {
        _logger.LogError(args.Exception, "Service Bus error on {EntityPath}", args.EntityPath);
        return Task.CompletedTask; // Abandon → retry → dead-letter
    }
}
```

### Dead-Letter Processing
```csharp
// Check dead-letter queue periodically
var client = new ServiceBusClient(connectionString);
var receiver = client.CreateReceiver(queueName, new ServiceBusReceiverOptions
{
    SubQueue = SubQueue.DeadLetter
});

await foreach (var msg in receiver.ReceiveMessagesAsync())
{
    _logger.LogError("Dead-lettered: {Reason} | Body: {Body}",
        msg.DeadLetterReason, msg.Body.ToString());
    await receiver.CompleteMessageAsync(msg);
}
```

## Retry Policy
- MaxDeliveryCount: 5 (queue setting)
- Lock duration: 5 minutes (for long-processing messages)
- Dead-letter after MaxDeliveryCount exceeded

## Required Settings
```json
{
  "ServiceBus": {
    "ConnectionString": "from-key-vault",
    "QueueName": "feature-name-queue",
    "MaxConcurrentCalls": 4,
    "PrefetchCount": 10
  }
}
```

## Anti-Patterns
- Never use connection string in code — always Key Vault
- Never process in `HandleErrorAsync` — only log
- Never use topics for commands (use queues)
- Never use synchronous sends in Blazor components
