Microservices Pattern Demo
Microservices patterns, built and running rather than just drawn as a diagram - a gateway, two services with their own databases, talking through events, with full tracing end to end.

Reference architectures are usually diagrams and prose - accurate, but static. This is the same small set of integration patterns built as a real, runnable system instead: place an order through the UI and watch it cross two services, a message broker, and two databases, then follow its own trace straight into Jaeger to see exactly what happened and when.
flowchart LR
person["Architect / developer (Person)"]
system["Microservices Pattern Demo (Software System)"]
jaeger[("Jaeger - trace storage (Software System)")]
person -->|Places orders, browses inventory| system
system -->|Exports OTLP spans| jaeger
classDef person fill:#0a6b3a,stroke:#064a28,color:#fff
classDef system fill:#1168bd,stroke:#0b4884,color:#fff
class person person
class system,jaeger system
There are five containers underneath that one system boundary, each doing one job -
including a direct call from orders-service to inventory-service, which checks and
reserves stock before an order is accepted. More on why that call exists further down,
under Part 2.
flowchart LR
client["Browser (UI)"]
gw["API Gateway (nginx)"]
orders["orders-service (FastAPI)"]
inventory["inventory-service (FastAPI)"]
broker[("Redis - broker + cache")]
ordersDb[("orders.db (SQLite)")]
inventoryDb[("inventory.db (SQLite) - stock + read model")]
jaeger[("Jaeger - traces")]
client --> gw
gw -->|"/orders"| orders
gw -->|"/inventory, /orders/summary"| inventory
gw -->|"/jaeger/"| jaeger
orders -->|reads/writes| ordersDb
inventory -->|reads/writes stock, read model| inventoryDb
inventory -->|"cache-aside"| broker
orders -->|"reserve/release (sync)"| inventory
orders -->|"publish: order_created + trace context"| broker
broker -->|subscribe| inventory
orders -.->|OTLP spans| jaeger
inventory -.->|OTLP spans| jaeger
classDef container fill:#1168bd,stroke:#0b4884,color:#fff
class gw,orders,inventory,broker,ordersDb,inventoryDb,jaeger container
API Gateway. The browser only ever talks to nginx. It routes /orders and /inventory to
the two services behind it and serves the UI itself - callers never call orders-service or
inventory-service directly, and either can move, scale, or restart without the gateway’s
routes changing.
Database-per-service. orders-service and inventory-service each keep their own SQLite
file. Neither reads the other’s table directly; the only thing that crosses the boundary between
them is the event described next. That separation is what lets either service be redeployed, or
rebuilt entirely, without coordinating a shared schema change.
Publish-subscribe. When an order is placed, orders-service publishes an order_created
event to Redis and returns - it has no idea whether anything is listening. inventory-service
happens to be the current subscriber: it records a fulfilment entry when the event arrives,
entirely on its own schedule - and, as of Part 3,
that same entry doubles as a read model. Either service could be stopped, replaced, or scaled
independently of the other. (Stock itself is handled differently as of
Part 2 - see below.)
That last pattern has one well-known problem: Redis pub/sub carries no trace context on its
own. Auto-instrumenting the publish/subscribe calls gives each one its own OpenTelemetry span,
but without extra work, inventory-service’s reaction to an event shows up as a trace completely
disconnected from the POST /orders that caused it - an easy thing to get wrong in any
event-driven system, not specific to this demo. The fix: orders-service writes the current
trace’s context into the event’s own JSON payload before publishing it, and inventory-service
reads it back out and starts its own span as a child of that same trace, rather than a new one.

The result is what that screenshot shows: one trace per order, spanning both services, not two unrelated ones that happen to share a timestamp. Every order placed through the demo links straight to its own trace - nothing to search for.
Two of the three patterns - API Gateway and Publish-Subscribe - are also named, vendor-documented entries in Architecture Governance Platform’s pattern glossary (Gateway Routing and Publisher-Subscriber respectively); this demo exists to show them actually working, not to replace those write-ups. Database-per-service has no equivalent in that glossary - it’s a general microservices decomposition pattern, not a cloud-vendor one. Architecture Governance Platform itself sits behind a login - the pattern catalogue is a public copy of the entries this demo is built from, plus the full 44-pattern glossary.
Part 2: resilience patterns adds Saga / Compensating Transaction, Circuit Breaker, and Retry - fixing this demo’s longest-standing known limitation (nothing ever checked whether stock existed before accepting an order), measured directly: a dependency failure drops from ~8 seconds of retries to under 100ms once the circuit breaker opens.
Part 3: CQRS and Cache-Aside adds a genuinely
separate read model (GET /orders/summary, built from the same event as Part 2’s fulfilment log,
not queried live) and a cache in front of GET /inventory - both demonstrated at the smallest
scale that still means something, reusing Redis for a second, unrelated job rather than adding
new infrastructure.
Want to see the same architecture on managed cloud services instead of Docker Compose? How to run event-driven microservices on Azure moves this exact system - gateway, per-service databases, event broker, tracing - onto Azure Container Apps, Service Bus, PostgreSQL Flexible Server and Application Insights, with the one limitation Redis pub/sub can’t fix: an event published while its consumer is down no longer gets lost. Its own companion page, Part 2 on Azure, adds the synchronous reservation call and two ways to get Circuit Breaker + Retry around it - porting the demo’s own code as-is, or Azure Container Apps’ built-in Dapr resiliency policies instead. Part 3 on Azure adds the read model and a cache - honestly, the one place this variant can’t reuse what Compose reuses, since Service Bus isn’t a cache the way Redis is.
The glossary provides an explanation for most of the technical terms mentioned in this demo.
Want to see a different pattern, built the same way? The Strangler Fig Demo is a separate, standalone demo: a legacy product catalog migrated one slice at a time behind a facade, with routing you can flip live. Not a Part of this demo - a different pattern, a different domain, kept deliberately separate.
Third in the series, Lost in Handover moves from integration patterns to an AI agent built on top of them - reasoning over a real parcel’s tracking history, honest about what it knows and what it doesn’t.