// Design Patterns · 19
Observer
Let many listeners react to one event without knowing them.
How it fits together
- OrderService publishes / notifies DomainEventHandler (after commit)
- SendConfirmation implements DomainEventHandler
- AwardLoyalty implements DomainEventHandler
- TrackAnalytics implements DomainEventHandler
Spot it
- A save method calls five unrelated services
- Adding a reaction means editing the publisher
- Polling to check whether state changed
It costs
The flow is harder to follow, and ordering and failure handling become implicit.
In code
public sealed record OrderPlaced(Guid OrderId, decimal Total);public interface IDomainEventHandler<in TEvent>{ Task HandleAsync(TEvent domainEvent, CancellationToken ct);}public sealed class SendConfirmation(IMailer mailer) : IDomainEventHandler<OrderPlaced>{ public Task HandleAsync(OrderPlaced domainEvent, CancellationToken ct) => mailer.SendOrderConfirmationAsync(domainEvent.OrderId, ct);}public sealed class OrderService( IOrderRepository orders, IEnumerable<IDomainEventHandler<OrderPlaced>> handlers){ public async Task PlaceAsync(Order order, CancellationToken ct) { await orders.SaveAsync(order, ct); var domainEvent = new OrderPlaced(order.Id, order.Total); foreach (var handler in handlers) { await handler.HandleAsync(domainEvent, ct); } }}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
The subject keeps a list of observers and notifies them when its state changes. It knows they exist, not what they do, so new reactions need no change to the publisher.
Event subscriptions in many languages hold a reference to every subscriber, so a long-lived publisher keeps forgotten subscribers alive: always unsubscribe. Also watch for handlers that raise more events until nobody can trace a single change.
Built-in events and observable streams are in-process observers. Once listeners live in other services, it becomes publish-subscribe over a broker, and you need an outbox and idempotent consumers to keep it reliable.
Ask for it
- subject / observer
- publish-subscribe
- domain event
- event handler
- observable stream
- unsubscribe
After an order is placed, OrderService calls email, loyalty and analytics directly. Raise an OrderPlaced domain event instead, with one DomainEventHandler per reaction, resolved from the DI container and dispatched after the transaction commits.