Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 42

    Behavioral Patterns in Event Systems

    Behavioral Patterns in Event Systems composes Observer, Command, State, Mediator, and Chain of Responsibility into coherent event-driven architecture.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Behavioral Patterns in Event Systems composes Observer, Command, State, Mediator, and Chain of Responsibility into coherent event-driven architecture. Production systems rarely use one pattern — Command carries intent in, Domain Events are facts out, State manages aggregate lifecycle, CoR validates pipelines, Mediator/Saga coordinates workflows.

    The story

    A logistics platform used Observer for everything — every change emitted events, every handler listened. After 18 months no one could trace state mutations. Reshaping around Commands (intent in), Domain Events (facts out), Saga Mediator per workflow, and explicit State on aggregates restored order and made on-call survivable.

    The business problem

    Observer-for-everything event systems become untraceable spaghetti:

    • No intent trace: pure pub/sub — who started the flow?
    • Circular cascades: handlers mutate each other in loops.
    • Scattered workflow: long-running logic in random listeners.
    • Hidden state machines: lifecycle rules buried in event handlers.

    The problem teams faced

    Compose behavioral patterns when:

    • Event-driven architecture lacks clear command vs event distinction.
    • Workflows span aggregates/services with no owning coordinator.
    • Validation/enrichment duplicated or skipped at boundaries.
    • Aggregate lifecycle rules live in subscribers instead of aggregate.

    Understanding the topic

    Intent: Assign each behavioral pattern a clear role in event systems.

    • Command — intent inward (PlaceOrder).
    • CoR pipeline — validate → authorize → enrich → audit.
    • Aggregate + State — business rules; emits Domain Events.
    • Domain Event — immutable fact (OrderPlaced).
    • Observer/Subscriber — projections, notifications, BI.
    • Mediator/Saga — coordinates next Command across workflow.

    Internal architecture

    Event-driven behavioral stack:

    text
    Command → [CoR: validate · authorize · audit]
    Order Aggregate (State machine)
    ↓ emits
    OrderPlaced (Domain Event)
    ┌─────────┴──────────┐
    ▼ ▼
    Projections Saga Mediator
    (Observer) → ReserveInventory Command
    → on failure: CancelOrder Command

    Visual explanation

    Three diagrams map patterns to event flow:

    Command → Aggregate → Event
    PlaceOrderCommand
    Intent in
    CoR pipeline
    Validate · auth
    Order aggregate
    State machine
    OrderPlaced event
    Fact out
    Command is intent; event is fact — never conflate.
    Saga Mediator workflow
    OrderPlaced
    Event triggers
    Saga Mediator
    Owns workflow
    ReserveInventory
    Next command
    PaymentFailed
    Compensate
    Saga coordinates — don't scatter workflow across observers.
    Pattern ownership
    Validation?
    CoR pipeline
    Lifecycle?
    State on aggregate
    Side effects?
    Observer subscribers
    Multi-step?
    Mediator Saga
    One pattern per concern — prevents Observer soup.

    Informative example

    Before / after — Observer soup to composed patterns:

    ts
    // ❌ Observer everything — untraceable cascades
    events.on("inventoryChanged", () => {
    orders.tryShip() // mutates orders
    })
    events.on("orderUpdated", () => {
    inventory.release() // circular cascade
    })
    // ✅ Composed behavioral patterns
    async function handle(cmd: PlaceOrderCommand) {
    await pipeline.run(cmd) // CoR: validate · auth
    const events = orderAggregate.handle(cmd) // State + rules
    await bus.publish(events) // OrderPlaced fact
    }
    saga.on(OrderPlaced, async (e) => {
    await bus.send(new ReserveInventoryCommand(e.orderId)) // Mediator
    })
    subscribers.on(OrderPlaced, projection.update) // Observer — read only

    Execution workflow

    1Event system request lifecycle
    1 / 5

    Command arrives

    CoR pipeline validates, authorizes, enriches.

    Never skip pipeline at any entry point.

    Real-world use

    Event-sourced banking, e-commerce fulfillment, ride-hailing dispatch, Axon/EventStoreDB, Kafka + Temporal sagas, NestJS CQRS module.

    Production case study

    Logistics platform reshape:

    • Scenario: Observer for everything; untraceable mutations after 18 months.
    • Decision: Commands in, events out, Saga per workflow, State on aggregates.
    • Outcome: On-call could trace flows; new reactions didn't edit publishers.
    • Metric: incident MTTR down 40% with correlation IDs on command/event chain.

    Trade-offs

    • Pro: clear ownership per pattern role; traceable flows.
    • Pro: aggregates enforce invariants; subscribers stay read-only.
    • Con: more upfront architecture than "just emit events."
    • Con: saga complexity — compensations, idempotency required.

    Decision framework

    • Command for intent; Domain Event for fact — always separate.
    • State on aggregate for lifecycle — not in subscribers.
    • Saga/Mediator when workflow spans steps/aggregates.
    • Observer for reactions that don't command back synchronously in loop.

    Best practices

    • Correlation ID from command through all events and saga steps.
    • Idempotent event handlers — at-least-once delivery.
    • Subscribers don't send commands that loop to same aggregate synchronously.
    • One saga owns each long-running workflow.

    Anti-patterns to avoid

    • Fat event handlers with business logic and state mutations.
    • Choreography soup — 12 listeners each issuing next command.
    • Command disguised as event ("PlaceOrderEvent" that triggers work).

    Common mistakes

    • Distributed saga without compensation — partial failure inconsistent state.
    • Read model subscriber mutating write model.
    • Missing outbox — lost events on crash after DB commit.

    Debugging tips

    • Trace correlation ID across command → events → saga commands.
    • Event audit log: who published, who consumed, handler duration.

    Optimization strategies

    • Separate hot in-process observers from cold async bus subscribers.
    • CoR pipeline cache for repeated validation on similar commands.

    Common misconceptions

    • Event-driven ≠ Observer only. Need Command, State, Saga too.
    • CQRS combines Command + Observer projections.
    • Choreography vs Saga Mediator — know when central coordination wins.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    4 questions
    1IntermediateQuestionCommand vs Domain Event?+

    Answer

    Command: intent to change state (PlaceOrder). Event: immutable fact that change happened (OrderPlaced). CQRS uses both.

    Follow-up

    Can handler emit command?
    2AdvancedQuestionWhere State machine lives?+

    Answer

    On aggregate handling command — not scattered in event subscribers. Subscribers react to facts; aggregate owns transitions.

    Follow-up

    OrderPlaced handler?
    3AdvancedQuestionSaga vs Observer chain?+

    Answer

    Saga/Mediator explicitly owns workflow and issues next commands with compensation logic. Observer chain is implicit — becomes untraceable spaghetti.

    Follow-up

    Logistics reshape?
    4IntermediateQuestionCoR role in event systems?+

    Answer

    Pre-process commands before aggregate: validate schema, authorize, enrich context, audit — pipeline at entry boundary.

    Follow-up

    Skip pipeline risk?

    Summary

    You can design event systems assigning each behavioral pattern a role and avoid Observer spaghetti. Tell the logistics 18-month reshape story — if you can do that, you own the Behavioral section.

    Ready to mark this lesson complete?Track your journey across the entire course.