PBO Tech

← Microservices Pattern Demo · builds on Part 2 on Azure

Part 3 on Azure: CQRS and Cache-Aside

This picks up where Part 2 on Azure left off. Assumes that guide’s setup is already done: orders-service and inventory-service as Container Apps, PostgreSQL Flexible Server, the synchronous reserve/release call in place.

CQRS: no new resource needed

The read model is just another table in the PostgreSQL database inventory-service already has - nothing new to provision. fulfilled_orders gains a stock_after column, written at the same point the Compose version writes it: when the order_created message is processed.

# inventory-service: record_fulfillment, PostgreSQL version
def record_fulfillment(order_id: str, item: str, qty: int):
    with db() as conn, conn.transaction():
        row = conn.execute("SELECT qty FROM stock WHERE item = %s", (item,)).fetchone()
        stock_after = row[0] if row else None
        conn.execute(
            "INSERT INTO fulfilled_orders (order_id, item, qty, stock_after, fulfilled_at) "
            "VALUES (%s, %s, %s, %s, now()) ON CONFLICT (order_id) DO NOTHING",
            (order_id, item, qty, stock_after))

@app.get("/orders/summary")
def order_summary():
    with db() as conn:
        rows = conn.execute(
            "SELECT order_id, item, qty, stock_after, fulfilled_at FROM fulfilled_orders "
            "ORDER BY fulfilled_at DESC").fetchall()
    return [{"order_id": r[0], "item": r[1], "qty": r[2], "stock_after": r[3], "fulfilled_at": r[4].isoformat()} for r in rows]

Same shape as the Compose version, two differences only: %s placeholders instead of ?, and ON CONFLICT (order_id) DO NOTHING in place of SQLite’s INSERT OR IGNORE - PostgreSQL’s equivalent for the same idempotency-against-redelivery reasoning. configure_azure_monitor() (Step 3d of the main guide) already instruments psycopg, so this query shows up in Application Insights the same way every other database call does - nothing extra needed to trace it.

Cache-Aside: the one place this variant can’t reuse what Compose reuses

Here’s the honest difference worth stating plainly: the Compose version’s ADR for this decision is literally “reuse Redis, since it’s already running as the broker.” On Azure, the broker is Service Bus, not Redis - there’s nothing already running to reuse. Cache-Aside on Azure needs a cache provisioned specifically for this.

flowchart LR
    reader["GET /inventory (Container App)"]
    cache[("Azure Cache for Redis")]
    store[("PostgreSQL: inventory")]
    writer["reserve / release (Container App)"]

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

    classDef container fill:#1168bd,stroke:#0b4884,color:#fff
    classDef managed fill:#999999,stroke:#6b6b6b,color:#fff
    class reader,writer container
    class cache,store managed
REDIS=redis-msdemo-$SUFFIX
az redis create -g $RG -n $REDIS -l $LOC --sku Basic --vm-size c0

REDIS_HOST=$(az redis show -g $RG -n $REDIS --query hostName -o tsv)
REDIS_KEY=$(az redis list-keys -g $RG -n $REDIS --query primaryKey -o tsv)

Basic C0 is the smallest, cheapest tier - fine for a demo, not for anything needing high availability (Basic has no SLA and no replica). Azure Cache for Redis requires TLS by default, on port 6380 rather than Redis’s usual 6379:

import redis
cache = redis.Redis(host=os.environ["REDIS_HOST"], port=6380,
                     password=os.environ["REDIS_KEY"], ssl=True)

The cache-aside logic itself - check, populate on miss with a TTL, invalidate on write - is identical to the Compose version’s, since it’s all application code with no Azure-specific behaviour. Pass REDIS_HOST/REDIS_KEY to inventory-service’s container the same way PGHOST/PGUSER are already passed in Step 4 of the main guide.

Worth knowing: Basic and Standard tier Azure Cache for Redis traditionally authenticate with an access key rather than a managed identity, unlike every other resource this demo’s Azure variant uses - Microsoft Entra ID authentication for Cache for Redis exists on some tiers, but check az redis -h and the current Azure docs for what your subscription’s tier actually supports before assuming it’s available, the same hedge the main guide makes about CLI flags drifting across versions. An access key stored as a plain environment variable is a real step down from this demo’s otherwise all-managed-identity posture - worth flagging if this were going further than a demo.

Verify

GW=https://$(az containerapp show -g $RG -n gateway --query properties.configuration.ingress.fqdn -o tsv)

# Cache-Aside
curl -sS $GW/inventory -D - -o /dev/null | grep -i x-cache   # MISS
curl -sS $GW/inventory -D - -o /dev/null | grep -i x-cache   # HIT

# CQRS
curl -sS -X POST $GW/orders -H 'Content-Type: application/json' -d '{"item":"widget","qty":1}'
curl -sS $GW/orders/summary

Unfamiliar with a term on this page? See the glossary for plain-English definitions, including the Azure-specific ones.