Course contentsShow
← C#
Lesson 1896 of 1,90817. Deployment and Cloud ArchitectureFree lesson

Event-Driven Architecture with Message Queues

Decouple services by offloading work to background workers via message brokers.

In this lesson, you will learn how to decouple your .NET microservices by offloading time-consuming tasks to background workers using message brokers.

The Problem: Synchronous Coupling

In a typical cloud-native application, one service often needs another to complete an action. If your OrderService waits for an EmailService to send a confirmation before responding to the user, you create a synchronous bottleneck. If the email provider is slow or down, the user experience suffers, and your systems become tightly coupled.

The Solution: Asynchronous Offloading

Instead of direct communication, the OrderService publishes an OrderPlaced event to a message broker (like RabbitMQ, Azure Service Bus, or Amazon SQS). The broker acts as a buffer, holding the message until a background worker is ready to process it.

By using an interface for your message producer, you decouple the producer from the consumption logic:

public async Task PlaceOrderAsync(Order order)
{
    // 1. Perform database operation
    await _dbContext.Orders.AddAsync(order);
    await _dbContext.SaveChangesAsync();

    // 2. Offload non-critical task to the queue
    var message = new OrderPlacedIntegrationEvent(order.Id, order.Email);
    await _messageBus.PublishAsync(message); 
    
    // The API returns success immediately to the user
}

Background Workers as Consumers

On the other side, you implement a BackgroundService (the worker) that listens to the queue. Because this runs in a separate process or container, it can scale independently based on the number of messages in the queue.

public class EmailWorker : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _messageBus.SubscribeAsync<OrderPlacedIntegrationEvent>(async (msg) => 
        {
            await _emailService.SendConfirmation(msg.Email);
        });
    }
}

By decoupling these services, you ensure that even if the EmailService experiences a performance regression or needs to be taken down for a deployment, your OrderService remains resilient. The messages simply accumulate in the queue and are processed as soon as the worker service recovers, preventing cascading failures across your infrastructure.

Key Takeaway: Use message queues to turn synchronous, blocking dependencies into asynchronous, fault-tolerant background tasks, allowing your services to evolve and scale independently.