Messaging Patterns
Microservices need to communicate without tight coupling. Asynchronous messaging decouples services in time — the sender doesn’t wait for the receiver.
Core Patterns
Section titled “Core Patterns”Point-to-Point (Queue)
Section titled “Point-to-Point (Queue)”One sender, one consumer. Each message is processed by exactly one consumer:
OrderService ──► [Queue] ──► EmailServiceUse for: work distribution, task offloading, load leveling.
Publish/Subscribe (Topic)
Section titled “Publish/Subscribe (Topic)”One publisher, multiple subscribers. Each subscriber gets a copy:
OrderService ──► [Topic] ──► EmailService ──► InventoryService ──► AuditServiceUse for: event broadcasting, domain events, fan-out notifications.
Request/Reply
Section titled “Request/Reply”Async request with a correlation ID and reply-to queue:
OrderService ──► [Request Queue] ──► PricingServiceOrderService ◄── [Reply Queue] ◄── PricingService(correlated by RequestId)RabbitMQ with MassTransit
Section titled “RabbitMQ with MassTransit”dotnet add package MassTransitdotnet add package MassTransit.RabbitMQDefine a message contract:
public record OrderPlaced(Guid OrderId, decimal Amount, string CustomerEmail);Configure MassTransit:
builder.Services.AddMassTransit(x =>{ x.AddConsumer<OrderPlacedConsumer>();
x.UsingRabbitMq((context, cfg) => { cfg.Host("rabbitmq://localhost", h => { h.Username("guest"); h.Password("guest"); });
cfg.ConfigureEndpoints(context); });});Publish an event:
public class OrderService{ private readonly IPublishEndpoint _publishEndpoint;
public OrderService(IPublishEndpoint publishEndpoint) => _publishEndpoint = publishEndpoint;
public async Task PlaceOrderAsync(Order order) { // save order... await _publishEndpoint.Publish(new OrderPlaced(order.Id, order.Amount, order.CustomerEmail)); }}Consume an event:
public class OrderPlacedConsumer : IConsumer<OrderPlaced>{ private readonly IEmailService _email;
public OrderPlacedConsumer(IEmailService email) => _email = email;
public async Task Consume(ConsumeContext<OrderPlaced> context) { await _email.SendConfirmationAsync( context.Message.CustomerEmail, context.Message.OrderId); }}Azure Service Bus
Section titled “Azure Service Bus”dotnet add package MassTransit.Azure.ServiceBus.Corebuilder.Services.AddMassTransit(x =>{ x.AddConsumer<OrderPlacedConsumer>();
x.UsingAzureServiceBus((context, cfg) => { cfg.Host("Endpoint=sb://your-namespace.servicebus.windows.net/;..."); cfg.ConfigureEndpoints(context); });});The same message contracts and consumers work unchanged across RabbitMQ and Azure Service Bus.
Outbox Pattern
Section titled “Outbox Pattern”Guarantees that a message is published if and only if the database transaction commits. Prevents lost events on crashes.
┌─────────────────────────────────────────┐│ BEGIN TRANSACTION ││ INSERT INTO orders (...) ││ INSERT INTO outbox_messages (...) ◄──┤── store message, not publish│ COMMIT │└─────────────────────────────────────────┘ ▼[Background Worker polls outbox] ▼[Publishes to message broker] ▼[Marks outbox message as processed]MassTransit Outbox (Entity Framework):
dotnet add package MassTransit.EntityFrameworkCore
x.AddEntityFrameworkOutbox<AppDbContext>(o =>{ o.UseSqlServer(); o.UseBusOutbox(); // use outbox for all publishes});Now any Publish or Send inside a request that uses DbContext is automatically outboxed:
public async Task PlaceOrderAsync(Order order){ _db.Orders.Add(order); await _publishEndpoint.Publish(new OrderPlaced(...)); // stored in outbox await _db.SaveChangesAsync(); // both committed atomically}Saga Pattern (Choreography vs Orchestration)
Section titled “Saga Pattern (Choreography vs Orchestration)”Long-running transactions that span multiple services.
Choreography — services react to events
Section titled “Choreography — services react to events”OrderService publishes OrderPlaced ├► PaymentService listens → publishes PaymentProcessed ├► InventoryService listens → publishes StockReserved └► ShippingService listens (waits for both above)No central coordinator. Simple, but hard to visualize the overall flow.
Orchestration — a saga coordinates the steps
Section titled “Orchestration — a saga coordinates the steps”public class OrderSaga : MassTransitStateMachine<OrderSagaState>{ public State Submitted { get; private set; } public State PaymentPending { get; private set; } public State Completed { get; private set; }
public Event<OrderPlaced> OrderPlaced { get; private set; } public Event<PaymentProcessed> PaymentProcessed { get; private set; }
public OrderSaga() { InstanceState(x => x.CurrentState);
Initially( When(OrderPlaced) .Then(ctx => ctx.Saga.OrderId = ctx.Message.OrderId) .Send(new Uri("queue:payment"), ctx => new ProcessPayment(ctx.Saga.OrderId)) .TransitionTo(PaymentPending) );
During(PaymentPending, When(PaymentProcessed) .TransitionTo(Completed) .Finalize() ); }}Dead Letter Queues
Section titled “Dead Letter Queues”Messages that can’t be processed go to a Dead Letter Queue (DLQ) for inspection:
cfg.ReceiveEndpoint("order-processing", e =>{ e.ConfigureConsumer<OrderPlacedConsumer>(context);
// Retry 3 times with exponential backoff before moving to DLQ e.UseMessageRetry(r => r.Exponential(3, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(2)));});Common DLQ strategies:
- Inspect and replay: fix the bug, re-publish from the DLQ
- Alert on DLQ growth: Prometheus/Azure Monitor metric threshold
Pattern Comparison
Section titled “Pattern Comparison”| Pattern | Coupling | Scalability | Traceability |
|---|---|---|---|
| Choreography | Low | High | Hard |
| Orchestration (Saga) | Medium | High | Easy |
| Point-to-Point Queue | Low | High | Medium |
| Pub/Sub | Very low | Very high | Medium |
| Request/Reply (async) | Low | High | Medium |
When to Use Each
Section titled “When to Use Each”| Scenario | Recommended Pattern |
|---|---|
| Email/notification after action | Pub/Sub event |
| Background job (resize image, generate report) | Point-to-point queue |
| Multi-step transaction across services | Saga (orchestration) |
| Fanout to many downstream systems | Pub/Sub topic |
| Reliable event with DB transaction | Outbox pattern |
| Async call that needs a result | Request/Reply |