Sitemap

Member-only story

Event-Driven Architecture in .NET C#: From the Observer pattern to the transactional outbox (2026)

15 min read2 days ago

--

Imagine your e-commerce is growing. When an order is placed, the shop must reserve the stock, prepare the shipment, send the confirmation email, update the loyalty points and tell the analytics. The first version puts it all where the order is saved:

// Naive approach - What NOT to do: the handler knows every reaction, and waits for each one
public sealed class NaivePlaceOrderHandler(
OrderingDbContext db,
IInventoryService inventory,
IShippingService shipping,
IEmailSender email,
ILoyaltyService loyalty,
IAnalyticsClient analytics)
{
public async Task<Guid> HandleAsync(Order order, string customerEmail, CancellationToken cancellationToken)
{
db.Orders.Add(order);
await db.SaveChangesAsync(cancellationToken);

await inventory.ReserveAsync(order.Id, cancellationToken);
await shipping.PrepareShipmentAsync(order.Id, cancellationToken);
await email.SendOrderConfirmationAsync(customerEmail, order.Id, cancellationToken); // a slow SMTP server slows the checkout
await loyalty.AddPointsAsync(order.CustomerId, order.Total, cancellationToken);
await analytics.TrackAsync("order-placed", order.Id, cancellationToken); // and every new reaction edits this class

return order.Id;
}
}

It works, today. The result?

  • Every new reaction edits the checkout, and every edit can break it.

--

--

Emanuele Longo
Emanuele Longo

Written by Emanuele Longo

.NET Software Developer driven by clean architecture and purposeful design.