← 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.