Module 1 · Ownership, Ordering and Failure · Lesson 2 of 2
Mediator: Make Booking Workflow Rules Explicit
Watch
Put the rule in one visible place
A booking request must reserve capacity, authorize a payment, and only then confirm the booking. A mediator-style coordinator makes these collaboration rules explicit. Individual components expose focused operations; they do not need to call every other participant directly. The goal is clear ownership, not a mandatory mediator library.
For this example, the invariant is: a confirmed booking has both a valid reservation and successful authorization. An email is a consequence of confirmation, not proof that the invariant holds.
Walk the success and failure paths
| Step | Success | Failure response |
|---|---|---|
| Reserve capacity | Keep a reservation reference. | Return unavailable; do not authorize payment. |
| Authorize payment | Keep an authorization reference. | On a definite decline, release the reservation and record failed cleanup for retry. On an ambiguous timeout, reconcile before deciding whether to release or continue. |
| Confirm booking | Store the confirmed state. | Reconcile the stored references; do not blindly repeat effects. |
| Notify customer | Record notification progress. | Retry notification without creating a second booking. |
These are illustrative business rules. A real product must decide reservation expiry, whether authorization or capture is intended, cancellation policy, and who owns reconciliation. A coordinator does not create a transaction across independent external systems by itself.
Why a timeout is ambiguous
Suppose the coordinator sends an authorization request and times out. The provider may have applied the request even though the response never arrived. Retrying with a new operation identity can create another effect. Use the same agreed idempotency identity for retries of the same logical operation and verify status where supported. Define the retention period, payload-mismatch handling and concurrency behavior of that identity.
A local database transaction can protect local state changes, but cannot make an arbitrary network call atomic with that transaction. If you choose an outbox or durable workflow, specify which state is committed together, how dispatch is retried and how consumers handle duplicates. Pattern names do not remove these obligations.
Keep the coordinator focused
The coordinator owns sequencing and decisions that connect participants. A pricing component still owns its calculation, and a reservation component owns its capacity rule. Avoid a single coordinator accumulating every unrelated business rule in the application. If the whole operation is a small, fixed sequence, a clearly named application-service method may be sufficient.
Exercise and worked answer
Confirmation succeeds but the email provider is unavailable. Should the system cancel the booking and ask the user to pay again?
Under the stated invariant, no: notification failure does not invalidate the successful reservation and authorization. Keep the booking confirmed, make its state visible to the user, and retry notification with duplicate-safe handling. If the business has a different rule, state it explicitly and test the compensation path.
Compare with Observer
Independent analytics or dashboard updates can observe a confirmed-booking event. Required reservation-before-authorization ordering belongs in the workflow. Use the workbook to practice distinguishing a notification from a dependency.
Before accepting a design, test one unavailable dependency, one ambiguous timeout, one duplicate request and one cleanup failure.
Trace a recovery record
For each logical booking, retain its stable operation identity, requested payload, reservation reference and expiry, authorization reference or pending result, completed steps, and required cleanup. If the same identity arrives with a different payload, reject the conflict rather than quietly reusing an earlier result. Concurrent requests must claim the logical operation atomically; a read-then-insert pre-check can race.
Example: reservation R is active; authorization request A times out; the coordinator records an unknown outcome. It queries or replays A using the provider's supported identity contract. If A succeeded, it checks that R is still valid before confirmation. If R expired, it follows the business-defined re-reservation or compensation policy instead of confirming an invalid booking. If a release or reversal fails, durable recovery work remains pending and observable.
An outbox can commit local booking state and a pending notification together. A worker may send the notification again after crashing before recording completion, so the recipient still needs duplicate-safe handling. Compensation is a new business operation, not a time machine that rolls back arbitrary external effects. See Microsoft Compensating Transaction.
This trace is a design exercise; no payment, reservation or database operation is executed here.
Video companion
Dev Leader: Mediator Design Pattern In Action!, by Nick Cosentino (2023), demonstrates an in-process C# chat mediator that routes messages between users. See the creator tutorial for supporting discussion.
Use the demo to identify who owns communication rules, then apply that ownership to reserve, authorize and confirm. The chat example does not implement booking compensation, idempotency or durable recovery. It is not a current MediatR package setup guide. Observer and Mediator solve different relationship problems; neither is universally more loosely coupled or better.
Analogy: a booking desk
A booking desk asks the venue to hold capacity, asks the payment desk to authorize, and confirms only when the required evidence is present. Each specialist owns its own rule. The booking desk owns the order of calls and the recovery plan, so the venue and payment desk need not call every other participant.
If the phone cuts out, the desk cannot assume the payment desk did nothing. It looks up the same request and records unfinished recovery. Sending a confirmation email is a separate consequence once the booking invariant holds.
The analogy explains responsibility, not transaction guarantees. Real services can crash, disagree temporarily, expire reservations or apply duplicate requests. A mediator class alone does not supply durable state or exactly-once effects.
Mediator cheat sheet
| Decision | Useful rule |
|---|---|
| Fit | Centralize interaction rules when collaborators otherwise depend on one another |
| Invariant | Confirm only with valid reservation and successful authorization under this contract |
| Local rules | Pricing owns prices; reservation owns capacity; coordinator owns the sequence |
| Unavailable capacity | Stop before payment authorization |
| Definite decline | Release the reservation; track failed cleanup |
| Ambiguous timeout | Keep the outcome unknown; reconcile the same logical operation |
| Duplicate request | Define identity scope, payload conflicts, retention and concurrent claims |
| Crash recovery | Persist references, completed steps and pending compensation |
| Outbox | Atomically record local state plus intent; expect duplicate dispatch |
| Notification failure | Preserve a valid confirmed booking; retry notification separately |
| Simpler alternative | A focused application-service method can be enough |
Interview test plan
Explain the expected state after unavailable capacity, definite decline, ambiguous authorization, expired reservation, duplicate concurrent request, failed compensation and notification redelivery. For every case, name the component that owns the next action.