Appearance
ADR-005: Azure Service Bus over RabbitMQ for Messaging
Status
Accepted
Date
2024-Q1
Context
Microtec ERP's microservices architecture (ADR-001) requires asynchronous messaging for:
- Integration events between services (e.g.,
EmployeeCreatedEvent→ Notification service) - Long-running background processing (import jobs, report generation)
- Retry and dead-letter handling for failed messages
- Ordered processing for workflows
The original implementation used RabbitMQ self-hosted on a VM as the message broker.
Problems with Self-Hosted RabbitMQ
| Problem | Impact |
|---|---|
| VM maintenance | Patching, upgrades, disk management |
| High availability setup | Clustering required separate ops work |
| Monitoring | Custom dashboards, no native Azure Monitor integration |
| Durability | Risk of message loss on VM failure without careful setup |
| Queue management | Manual exchange/queue topology management |
| Support burden | DevOps team spent significant time on RabbitMQ issues |
Candidates Evaluated
| Broker | Type | Key Properties |
|---|---|---|
| RabbitMQ (current) | Self-hosted | Full control, AMQP native, operational burden |
| Azure Service Bus | Azure PaaS | Fully managed, dead-letter built-in, Azure Monitor integration |
| Azure Event Hubs | Azure PaaS | High-throughput streaming, not suitable for ERP transactional patterns |
| Azure Storage Queues | Azure PaaS | Extremely simple, limited features (no topics/subscriptions) |
| MassTransit + any broker | Abstraction | Broker-agnostic; we already used MassTransit |
MassTransit as the Enabler
The ERP already used MassTransit as the messaging abstraction layer. MassTransit supports both RabbitMQ and Azure Service Bus behind the same consumer/publisher interface.
This meant migration could be done by changing configuration only — no code changes to consumers or publishers.
Decision
Migrate from self-hosted RabbitMQ to Azure Service Bus Standard tier, using MassTransit as the abstraction layer.
Key decisions:
- MassTransit abstraction preserved: Consumer and publisher code unchanged
- ASB Standard tier: Sufficient for ERP messaging patterns (not streaming)
- Dead-letter queue: Use ASB's built-in DLQ — no custom implementation needed
- Retries: Configured via MassTransit's retry policy, transparent to consumers
- Topics + Subscriptions: Used for fan-out events (e.g.,
TenantCreatedEventto multiple subscribers)
MassTransit Configuration
csharp
// Before (RabbitMQ)
services.AddMassTransit(x =>
{
x.UsingRabbitMq((ctx, cfg) =>
{
cfg.Host(configuration["RabbitMQ:Host"], h =>
{
h.Username(configuration["RabbitMQ:Username"]);
h.Password(configuration["RabbitMQ:Password"]);
});
cfg.ConfigureEndpoints(ctx);
});
});
// After (Azure Service Bus) — consumer code UNCHANGED
services.AddMassTransit(x =>
{
x.UsingAzureServiceBus((ctx, cfg) =>
{
cfg.Host(configuration["ServiceBus:ConnectionString"]);
cfg.ConfigureEndpoints(ctx);
});
});Topic/Subscription Pattern
ASB Namespace: mic-erp-{env}-asb
├── Topics:
│ ├── employee-created (fan-out: Notification, Workflow, HR-archive)
│ ├── invoice-approved (fan-out: Integration, Notification)
│ ├── tenant-created (fan-out: All services for seeding)
│ └── workflow-completed (fan-out: Originating service)
└── Queues:
├── import-processing (point-to-point: Import service)
├── report-generation (point-to-point: Reporting service)
└── zatca-submission (point-to-point: ZATCA integration)Consequences
Positive
- Zero ops overhead: No VMs, no clustering, no disk management
- Built-in dead-letter: Automatic dead-letter queue with metadata — no custom DLQ implementation
- Azure Monitor native: Queue depth, message count, DLQ alerts in Azure Monitor dashboards
- SLA: 99.9% uptime SLA from Microsoft (vs "best effort" on self-hosted VM)
- Transparent migration: MassTransit abstraction made the migration code-change-free
- Scalable throughput: ASB Standard handles up to 10 million operations/month
- RBAC integration: Azure managed identity for authentication — no connection string credentials
Negative
- Vendor lock-in: Azure Service Bus is Azure-only; RabbitMQ runs anywhere
- On-premise limitation: Enterprise customers wanting fully on-premise deployments cannot use ASB; would require RabbitMQ fallback
- Cost: ASB Standard ~$9.81/month base + per-operation charges; RabbitMQ was "free" (just VM cost)
- Latency: Slightly higher than RabbitMQ in same-datacenter scenarios (~2ms vs ~0.5ms)
- Namespace limits: 10,000 topics per namespace — unlikely to hit for ERP, but a ceiling exists
Neutral
- The
Microtec.MessagingNuGet package wraps the MassTransit/ASB configuration - Existing consumers implement
IIntegrationEventHandler<T>— no changes needed - Connection strings stored in Key Vault; referenced via
keyvaultref:in ACA environment variables
Configuration Reference
json
{
"ServiceBus": {
"ConnectionString": "<from KV: ServiceBus--ConnectionString>",
"RetryPolicy": {
"MaxRetryAttempts": 3,
"RetryInterval": "00:00:30",
"MaxRetryInterval": "00:05:00"
},
"DeadLetterQueueTtl": "14.00:00:00"
}
}Key Vault Secret Naming
ServiceBus--ConnectionString → Endpoint=sb://mic-erp-{env}-asb.servicebus.windows.net/;SharedAccessKeyName=...Migration History
The RabbitMQ → ASB migration was performed in Session 8 (2024-Q1). The MassTransit abstraction made this a one-session migration with zero consumer code changes.
Legacy references to RabbitMQ__Host or RabbitMQ__Username in any remaining config files should be removed — they are inert but may cause confusion.
Related ADRs
- ADR-001: Microservices (ASB provides the inter-service communication backbone)
- ADR-006: NuGet Packages (
Microtec.Messagingencapsulates MassTransit/ASB setup)