Behavioral
Observer: one order, many listeners
Place an order once and every subscribed service hears it. A service that leaves the list hears nothing.
- Planted
- Last tended

What Is It?
Observer keeps a subject and a list of listeners. The subject has one job: when something happens, walk the list and tell each listener. The listener decides what that means.
In this version the subject is OrderService. The event is OrderPlaced. Email, inventory, and the audit log are listeners. The order does not send mail, reserve stock, or write a log line. It only announces that the order was placed.
When Is It Used?
Use it when one fact in the domain has several independent consequences, and those consequences change more often than the fact. A new receipt email should not require a new method on the order. A listener you can subscribe and unsubscribe is enough.
Skip it when the caller must know the result. If placing an order has to fail because inventory rejected the reservation, that is a direct call with a return value, not a notification. Observer is a poor place to hide a required answer.
Interactive Demo
EmailService is now on the list. Placing an order will hand it a receipt to send.
OrderService sits on the left. EmailService, InventoryService, and AuditLog appear on the right only after you subscribe them. Publish sends OrderPlaced along the lines to the services that are still in the list.
- EmailService joins the listener list.
- InventoryService joins the same list.
- AuditLog joins. The readout shows 3 listeners.
- Place sends OrderPlaced only to services still subscribed.
0 listeners
Code
Code
Read onlyPatterns/Behavioral/Observer/OrderService.cs
Read only
public sealed class OrderService
{
private readonly List<IOrderListener> listeners = new();
public void Subscribe(IOrderListener listener)
{
if (!listeners.Contains(listener))
listeners.Add(listener);
}
public void Unsubscribe(IOrderListener listener)
{
listeners.Remove(listener);
}
public void Place(Order order)
{
var placed = new OrderPlaced(order.Id);
foreach (var listener in listeners)
listener.OnOrderPlaced(placed);
}
}Patterns/Behavioral/Observer/EmailService.cs
Read only
public sealed class EmailService : IOrderListener
{
public void OnOrderPlaced(OrderPlaced placed)
{
_outbox.Enqueue(new ReceiptMail(placed.OrderId));
}
}Advantages and Disadvantages
+ Advantages
- OrderService stays the same when a new listener appears.
- Each listener can be tested with a fake OrderPlaced and no order class.
- Unsubscribing is a list removal, so a retired service stops receiving events.
− Disadvantages
- The order of listeners becomes an accidental contract if one of them depends on another.
- A listener that throws can stop the rest of the list unless you isolate the call.
- Failures are easy to miss, because the subject does not wait for a result.
Tips
- 01Name the event after the fact that already happened, such as OrderPlaced, not after the work a listener will do.
- 02Give each listener its own try block if one failure must not cancel the others.
- 03Do not let a listener call back into Place. That re-enters the list you are already walking.
Next up
Next in the path: RAG Pipeline Design: Chunking, Retrieval, Reranking, and Generation