Introduction

In modern microservices architecture using .NET and ASP.NET Core, one of the biggest challenges developers face is ensuring reliable message delivery between services. When your application performs a database operation and also needs to send a message (for example, to a message broker like Kafka or RabbitMQ), there is always a risk of failure.

For example:

This leads to data inconsistency, which is a serious problem in distributed systems.

To solve this issue, we use the Outbox Pattern in .NET microservices.

In this article, we will understand the Outbox Pattern in simple words and learn how to implement it step-by-step using ASP.NET Core and Entity Framework Core.

What is the Outbox Pattern?

The Outbox Pattern is a design pattern used to ensure that database changes and messages are saved together reliably.

In simple words

Instead of sending messages directly:

This ensures:

Why Do We Need the Outbox Pattern?

In a typical microservices system:

But what if:

This creates inconsistency.

The Outbox Pattern solves this by:

Real-Life Example

Imagine an e-commerce system:

Without Outbox:

With Outbox:

How Outbox Pattern Works

The flow is simple:

  1. Application saves business data

  2. Application also saves event in Outbox table

  3. Both operations happen in a single transaction

  4. Background worker reads Outbox table

  5. Publishes messages to message broker

  6. Marks message as processed

This guarantees eventual consistency in distributed systems.

Step 1: Create Outbox Table

First, create an Outbox entity.

public class OutboxMessage
{
    public Guid Id { get; set; }
    public string Type { get; set; }
    public string Payload { get; set; }
    public DateTime CreatedAt { get; set; }
    public bool Processed { get; set; }
}

Add it to DbContext:

public DbSet<OutboxMessage> OutboxMessages { get; set; }

Step 2: Save Data and Event Together

When saving business data, also save the event.

public async Task CreateOrder(Order order)
{
    _context.Orders.Add(order);

    var message = new OutboxMessage
    {
        Id = Guid.NewGuid(),
        Type = "OrderCreated",
        Payload = JsonSerializer.Serialize(order),
        CreatedAt = DateTime.UtcNow,
        Processed = false
    };

    _context.OutboxMessages.Add(message);

    await _context.SaveChangesAsync();
}

Key point

Both operations happen in one transaction, ensuring reliability.

Step 3: Create Background Service to Process Outbox

Now create a background worker.

public class OutboxProcessor : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public OutboxProcessor(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _serviceProvider.CreateScope();
            var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            var messages = await context.OutboxMessages
                .Where(x => !x.Processed)
                .ToListAsync();

            foreach (var message in messages)
            {
                // Publish message (simulate)
                Console.WriteLine($"Publishing: {message.Type}");

                message.Processed = true;
            }

            await context.SaveChangesAsync();

            await Task.Delay(5000);
        }
    }
}

Step 4: Register Background Service

builder.Services.AddHostedService<OutboxProcessor>();

Step 5: Publish Message to Broker (Optional)

Instead of Console.WriteLine, integrate with:

Example:

await _messageBus.PublishAsync(message.Payload);

Handling Failures and Retries

To make the system robust:

Add fields like:

public int RetryCount { get; set; }
public string Error { get; set; }

Benefits of Outbox Pattern in .NET

Common Challenges

Best Practices

Real-World Use Case

In a payment system:

Even if messaging fails initially, it will retry and succeed.

Summary

The Outbox Pattern in .NET microservices is a powerful technique to ensure reliable message delivery and maintain data consistency across distributed systems. By storing events in a database table and processing them asynchronously using a background service, you can avoid message loss and build resilient, scalable applications. This pattern is essential for modern ASP.NET Core microservices that rely on event-driven architecture and message brokers.