Skip to content

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

ProblemImpact
VM maintenancePatching, upgrades, disk management
High availability setupClustering required separate ops work
MonitoringCustom dashboards, no native Azure Monitor integration
DurabilityRisk of message loss on VM failure without careful setup
Queue managementManual exchange/queue topology management
Support burdenDevOps team spent significant time on RabbitMQ issues

Candidates Evaluated

BrokerTypeKey Properties
RabbitMQ (current)Self-hostedFull control, AMQP native, operational burden
Azure Service BusAzure PaaSFully managed, dead-letter built-in, Azure Monitor integration
Azure Event HubsAzure PaaSHigh-throughput streaming, not suitable for ERP transactional patterns
Azure Storage QueuesAzure PaaSExtremely simple, limited features (no topics/subscriptions)
MassTransit + any brokerAbstractionBroker-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:

  1. MassTransit abstraction preserved: Consumer and publisher code unchanged
  2. ASB Standard tier: Sufficient for ERP messaging patterns (not streaming)
  3. Dead-letter queue: Use ASB's built-in DLQ — no custom implementation needed
  4. Retries: Configured via MassTransit's retry policy, transparent to consumers
  5. Topics + Subscriptions: Used for fan-out events (e.g., TenantCreatedEvent to 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.Messaging NuGet 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.


  • ADR-001: Microservices (ASB provides the inter-service communication backbone)
  • ADR-006: NuGet Packages (Microtec.Messaging encapsulates MassTransit/ASB setup)

Internal Documentation — Microtec Platform Team