PBO Tech

← Microservices Pattern Demo

Pattern catalogue

This demo’s write-up is backed by real entries in Architecture Governance Platform - reference architectures, a principle, and the external pattern glossary they’re cross-linked to. That platform normally requires a login, so this page is a public copy of the parts that describe this demo specifically: condensed from the full artifacts, not a live mirror. The glossary below is reproduced in full, since it’s already public, vendor-documented reference material. (For plain-English definitions of the terms themselves - Saga, Circuit Breaker and the rest - see the glossary page instead; this page is the Azure vendor catalogue specifically.)

From Architecture Governance Platform

Reference architecture: Barebones microservices (API Gateway + database-per-service + pub/sub)

Demonstrates three recurring integration/decomposition patterns together, as a small runnable system rather than a diagram alone: a single entry point in front of independently-owned services, each with its own data store, coordinating through events rather than direct calls. Use this shape when services genuinely need to evolve, deploy, and scale on separate schedules, and when “eventually consistent” is an acceptable answer to “when does inventory reflect this order”. Don’t reach for it when a single service with one database would do the job - the coordination and eventual-consistency cost is real and only pays off once there’s a genuine reason to split.

flowchart LR
    client["Browser (UI)"]
    gw["API Gateway (nginx)"]
    orders["orders-service (FastAPI)"]
    inventory["inventory-service (FastAPI)"]
    broker[("Redis - order_created topic")]
    ordersDb[("orders.db (SQLite)")]
    inventoryDb[("inventory.db (SQLite)")]

    client --> gw
    gw -->|"/orders, /inventory"| orders
    gw -->|"/inventory"| inventory
    orders -->|reads/writes| ordersDb
    inventory -->|reads/writes| inventoryDb
    orders -->|"publish: order_created + trace context"| broker
    broker -->|subscribe| inventory

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    class gw,orders,inventory,broker,ordersDb,inventoryDb container

Two of its three components are already named, vendor-documented entries in the glossary below: API Gateway is a concrete instance of Gateway Routing (route requests to multiple services behind one endpoint); the order/inventory handoff is Publisher-Subscriber. Database-per-service has no equivalent in Microsoft’s glossary - it’s a general microservices decomposition pattern, not a cloud-vendor one.

Reference architecture: Trace context propagation across an event-driven boundary

Any system using asynchronous messaging (pub/sub, a queue, an event stream) between a producer and a consumer loses the connection between them in distributed tracing by default - auto-instrumentation gives the publish call and the consume-and-react work their own spans, but nothing links them into one trace. Use this pattern whenever a trace needs to show the whole journey of one unit of work - “this order led to this stock update” - across an asynchronous boundary, not just each side in isolation.

flowchart LR
    producer["Producer (Container)"]
    broker[("Message broker / event stream (Container)")]
    consumer["Consumer (Container)"]
    tracing[("Trace storage (Container)")]

    producer -->|"1: inject trace context into event payload"| broker
    broker -->|"2: deliver event (payload includes trace context)"| consumer
    consumer -->|"3: extract context, start child span"| consumer
    producer -.->|exports spans| tracing
    consumer -.->|exports spans, linked by extracted context| tracing

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    class producer,broker,consumer,tracing container

Demonstrated concretely in this demo using OpenTelemetry’s Python SDK (opentelemetry.propagate.inject()/extract()) over Redis pub/sub - the same inject/extract API exists in every OpenTelemetry language SDK and is broker-agnostic; nothing about it depends on Redis specifically. The anti-pattern worth naming directly: assuming auto-instrumentation alone handles this. It doesn’t, for any broker without built-in trace-context propagation support - and injecting the context but forgetting to extract it (or vice versa) produces no error anywhere, just two disconnected traces that are easy to miss in code review.

Principle: Propagate trace context explicitly across asynchronous integrations

Statement. Any integration that crosses an asynchronous boundary - a message queue, a pub/sub topic, an event stream - must carry the producer’s trace context in the message itself, and the consumer must extract it and continue that trace, not start a new one. Auto-instrumentation of the publish/subscribe calls alone is not sufficient and must not be treated as though it were.

Rationale. Distributed tracing’s whole value is showing one unit of work’s full journey. Without explicit propagation, an asynchronous boundary silently breaks that journey into two unrelated traces that happen to share roughly the same timestamp - easy to miss in code review, and only obvious once someone is actually trying to debug a real incident by reading a trace that turns out to stop short of where the problem actually was.

Reference architecture: Saga / Compensating Transaction for a synchronous reservation

Added in Part 2 of this demo. When two services each own their own database, there’s no distributed transaction spanning both - a multi-step operation that touches both can succeed partway and leave them inconsistent. Use this pattern when a request needs to reserve a resource in one service, then run a check that can only happen after that reservation, and must undo the reservation cleanly if that check fails.

flowchart LR
    caller["Caller (Container)"]
    resourceOwner["Resource owner (Container)"]
    store[("Reserved resource (Container)")]

    caller -->|"1: reserve"| resourceOwner
    resourceOwner -->|atomically decrements| store
    caller -->|"2: downstream check"| caller
    caller -->|"3a: check passed - proceed"| caller
    caller -->|"3b: check failed - release"| resourceOwner
    resourceOwner -->|undoes the reservation| store

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    class caller,resourceOwner,store container

Demonstrated with a plain atomic SQL UPDATE ... WHERE stock >= ? (reserve) and its inverse (release) over a synchronous HTTP call - no saga orchestration framework needed at this scale; the caller itself is the orchestrator. The anti-pattern worth naming: treating release as a no-op that can’t itself fail - if it fails, the reservation is left stuck with nothing giving it back; a real implementation needs to handle that explicitly.

Reference architecture: Retry with Circuit Breaker for a synchronous dependency

Also added in Part 2. Retry alone means a persistent outage costs every caller the full retry price, on every request. Circuit Breaker alone means a single transient blip trips it unnecessarily. Composed together - circuit breaker wrapping the whole retry sequence as one logical attempt - each covers the other’s weak spot.

flowchart LR
    caller["Caller (Container)"]
    breaker{"Circuit breaker: closed / open / half-open"}
    dependency["Dependency (Container)"]

    caller -->|call| breaker
    breaker -->|closed: attempt, up to 3x with backoff| dependency
    breaker -->|open: fail immediately, no network call| caller
    dependency -->|3 consecutive failed attempts| breaker

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    class caller,dependency container

Hand-rolled in this demo rather than a library, deliberately, so the mechanism is visible to a reader rather than hidden behind a decorator - about 60 lines in total. Measured directly: three failed attempts each took roughly 8 seconds (a full retry sequence against a dead connection), then the circuit opened and a fourth request failed in under 100 milliseconds instead.

All four Part 2 concepts are themselves named entries in the glossary below - Saga, Compensating Transaction, Circuit Breaker, and Retry - demonstrated here working together, not replacing their canonical write-ups.

Reference architecture: CQRS: a read model built from an existing event

Added in Part 3 of this demo. Database-per-service means a query needing data from more than one service’s database can’t be answered with a single join. CQRS separates the model used to make changes from the model used to answer reads - the read side built in advance from events, shaped for the question it needs to answer, rather than computed live from the write side’s own tables.

flowchart LR
    writer["Write side (Container)"]
    writeDb[("Write model (Container)")]
    event[("Domain event (Container)")]
    projector["Read model projector (Container)"]
    readDb[("Read model (Container)")]
    reader["Query (Container)"]

    writer -->|command| writeDb
    writer -->|publishes| event
    event -->|consumed by| projector
    projector -->|builds/updates| readDb
    reader -->|query| readDb

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    class writer,writeDb,event,projector,readDb,reader container

Demonstrated here by reusing an event the write side already publishes for an unrelated reason (Part 2’s fulfilment log), rather than introducing a new event just for this - the simplest possible version of the pattern: one read model, one existing event source, same database as the write side’s own tables.

Reference architecture: Cache-Aside for a frequently-read, infrequently-changed value

Also added in Part 3. A value read far more often than it changes can be served from a cache most of the time, falling back to the real data store only on a miss.

flowchart LR
    reader["Read (Container)"]
    cache[("Cache (Container)")]
    store[("Data store (Container)")]
    writer["Write (Container)"]

    reader -->|"1: check"| cache
    cache -->|"2: miss - read, then populate"| store
    writer -->|"on write: invalidate"| cache

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    class reader,cache,store,writer container

Demonstrated here using Redis - already running in this architecture as the event broker, given a second, unrelated job as a cache store. Measured directly: curl -i .../inventory twice in a row shows MISS then HIT, and a write in between drops it back to MISS.

Both Part 3 concepts are themselves named entries in the glossary below - CQRS and Cache-Aside - demonstrated here at the smallest scale that still means something, not replacing their canonical write-ups.

Azure Cloud Design Patterns glossary

The full 44-pattern catalogue from Microsoft’s Azure Architecture Center - explicitly technology-agnostic despite “Azure” in the name, Microsoft’s own framing. Reproduced here in full since it’s already public reference material; Architecture Governance Platform links out to the same source rather than duplicating it.

Pattern Pillars Summary
Ambassador Reliability, Security Create helper services that send network requests on behalf of a consumer service or application.
Anti-Corruption Layer Operational Excellence Implement a façade or adapter layer between a modern application and a legacy system.
Asynchronous Request-Reply Performance Efficiency Decouple back-end processing from a front-end host, for when back-end processing must be asynchronous but the front end requires a clear and timely response.
Backends for Frontends Reliability, Security, Performance Efficiency Create separate backend services for specific frontend applications or interfaces.
Bulkhead Reliability, Security, Performance Efficiency Isolate elements of an application into pools so that if one fails, the others continue to function.
Cache-Aside Reliability, Performance Efficiency Load data on demand into a cache from a data store.
Choreography Operational Excellence, Performance Efficiency Let individual services decide when and how a business operation is processed, instead of depending on a central orchestrator.
Circuit Breaker Reliability, Performance Efficiency Handle faults that might take a variable amount of time to fix when an application connects to a remote service or resource.
Claim Check Reliability, Security, Cost Optimization, Performance Efficiency Split a large message into a claim check and a payload to avoid overwhelming a message bus.
Compensating Transaction Reliability Undo the work performed by a sequence of steps that collectively form an eventually consistent operation.
Competing Consumers Reliability, Cost Optimization, Performance Efficiency Enable multiple concurrent consumers to process messages that they receive on the same messaging channel.
Compute Resource Consolidation Cost Optimization, Operational Excellence, Performance Efficiency Consolidate multiple tasks or operations into a single computational unit.
CQRS Performance Efficiency Separate operations that read data from those that update data by using distinct interfaces.
Deployment Stamps Operational Excellence, Performance Efficiency Deploy multiple independent copies of application components, including data stores.
Event Sourcing Reliability, Performance Efficiency Use an append-only store to record a full series of events that describe actions taken on data in a domain.
External Configuration Store Operational Excellence Move configuration information out of an application deployment package to a centralized location.
Federated Identity Reliability, Security, Performance Efficiency Delegate authentication to an external identity provider.
Gatekeeper Security, Performance Efficiency Protect applications and services by using a dedicated host instance to validate and sanitize requests before forwarding them to private back ends.
Gateway Aggregation Reliability, Security, Operational Excellence, Performance Efficiency Use a gateway to aggregate multiple individual requests into a single request.
Gateway Offloading Reliability, Security, Cost Optimization, Operational Excellence, Performance Efficiency Offload shared or specialized service functionality to a gateway proxy.
Gateway Routing Reliability, Operational Excellence, Performance Efficiency Route requests to multiple services by using a single endpoint.
Geode Reliability, Performance Efficiency Deploy back-end services across geographically distributed nodes. Each node can handle client requests from any region.
Health Endpoint Monitoring Reliability, Operational Excellence, Performance Efficiency Implement functional checks in an application that external tools can access through exposed endpoints at regular intervals.
Idempotent Consumer Reliability Handle duplicate message delivery so that processing a message multiple times has the same effect as processing it once.
Index Table Reliability, Performance Efficiency Create indexes over the fields in data stores that queries frequently reference.
Leader Election Reliability Coordinate actions in a distributed application by electing one instance as the leader, which manages a collection of collaborating task instances.
Materialized View Performance Efficiency Generate prepopulated views over the data in one or more data stores when the data is poorly formatted for required query operations.
Messaging Bridge Cost Optimization, Operational Excellence Build an intermediary to enable communication between messaging systems that are otherwise incompatible.
Pipes and Filters Reliability Break down a task that performs complex processing into a series of separate elements that can be reused.
Priority Queue Reliability, Performance Efficiency Prioritize requests sent to services so that requests with a higher priority are processed more quickly.
Publisher-Subscriber Reliability, Security, Cost Optimization, Operational Excellence, Performance Efficiency Enable an application to announce events to multiple consumers asynchronously, without coupling senders to receivers.
Quarantine Security, Operational Excellence Ensure that external assets meet a team-agreed quality level before the workload consumes them.
Queue-Based Load Leveling Reliability, Cost Optimization, Performance Efficiency Use a queue that creates a buffer between a task and a service to smooth intermittent heavy loads.
Rate Limiting Reliability Avoid or minimize throttling errors by controlling the consumption of resources.
Retry Reliability Enable applications to handle anticipated temporary failures by retrying failed operations.
Saga Reliability Manage data consistency across microservices in distributed transaction scenarios.
Scheduler Agent Supervisor Reliability, Performance Efficiency Coordinate a set of actions across distributed services and resources.
Sequential Convoy Reliability Process a set of related messages in a defined order without blocking other message groups.
Sharding Reliability, Cost Optimization Divide a data store into a set of horizontal partitions or shards.
Sidecar Security, Operational Excellence Deploy components into a separate process or container to provide isolation and encapsulation.
Static Content Hosting Cost Optimization Deploy static content to a cloud-based storage service for direct client delivery.
Strangler Fig Reliability, Cost Optimization, Operational Excellence Incrementally migrate a legacy system by gradually replacing pieces of functionality with new applications and services.
Throttling Reliability, Security, Cost Optimization, Performance Efficiency Control the consumption of resources from applications, tenants, or services.
Valet Key Security, Cost Optimization, Performance Efficiency Use a token or key to provide clients with restricted, direct access to a specific resource or service.