connecting…
reconnecting
waiting for snapshot…
← Back home
Lab

Eight microservices, one cluster.

.NET 9 + Aspire orchestration, MassTransit event mesh, Postgres + Redis + RabbitMQ + Vault. Real production patterns, not slideware. The static reference is below; the live cluster under fault injection is on /chaos.

The cluster

One BFF, five microservices, eight infra dependencies.

Every browser request lands on the BFF. The BFF fans out over HTTP to the five microservices, which own their own Postgres schemas. Cross-service coordination flows through RabbitMQ via MassTransit. Catalog reads go through Redis as an L2 cache. Identity and the BFF lease dynamic credentials from Vault.

Each service

What every microservice owns.

Each service has a single responsibility, its own Postgres schema, and the patterns that responsibility forces. Stack, patterns, and a click-driven demo for the headline pattern.

Single gateway from the browser to every microservice. Rate-limit, circuit-break, idempotency-replay, and the chaos fault-injection handler all live here.

ASP.NET Core 9 Polly v8 SignalR YARP-style proxy
Patterns this service owns
Rate limit (token bucket)
Sliding-window token bucket per session. Replies 429 + Retry-After when the bucket empties; refills steadily.
Circuit breaker
Polly StandardResilienceHandler wraps every outbound HttpClient. Opens after consecutive failures, half-opens on probe success.
Drive it · headline pattern

JWT issuance, refresh, JTI revocation, and dynamic Postgres credentials sourced from Vault.

ASP.NET Core 9 EF Core HashiCorp Vault OpenIddict-style JTI
Patterns this service owns
Vault dynamic credentials
Postgres connection strings rotate every hour via Vault leases. The connection interceptor swaps the active credential without dropping in-flight queries.
Drive it · headline pattern

Products, stock, OCC. Two replicas via Aspire WithReplicas. The hot path for every browse, every checkout.

ASP.NET Core 9 EF Core Postgres xmin HybridCache (Redis L2)
Patterns this service owns
Optimistic concurrency (xmin)
Postgres `xmin` shadow column is the EF concurrency token. Concurrent updates → exactly one wins, the rest get 412.
Cache invalidation
L1 in-process + L2 Redis. Invalidations propagate via a Redis pub/sub channel so all replicas converge.
Drive it · headline pattern

Idempotent claim creation. Owns the public idempotency contract for the storefront.

ASP.NET Core 9 EF Core Postgres ON CONFLICT
Patterns this service owns
Idempotency via ON CONFLICT
`INSERT … ON CONFLICT (key) DO UPDATE … RETURNING claim_id, (xmax = 0)` makes the first writer win and every replay return the same claim id without a transaction.
Drive it · headline pattern

Stripe + PayPal webhook processors. Transactional outbox for outbound MassTransit events.

ASP.NET Core 9 EF Core MassTransit Stripe.NET PayPalClient
Patterns this service owns
Transactional outbox
Webhook handler writes the payment row + an OutboxMessage in one transaction. A background worker dispatches the event to RabbitMQ; if dispatch fails the next pass retries.
Drive it · headline pattern

Checkout orchestrator

view on github →

The saga state machine. Coordinates StockReservation → PaymentSession → Capture → Complete, with a compensation arc on every step.

ASP.NET Core 9 MassTransit Saga State Machine EF Core (saga persistence)
Patterns this service owns
Place-order saga
State machine driven by published events. Every state transition is persisted; every failure path has a named compensating event. Visualised below.
Drive it · headline pattern
Message flow

The place-order saga, choreographed.

MassTransit state machine. Each event is published, each consumer transitions state, each failure has a named compensating event. No 2PC. No distributed locks. Run a real saga with a forced failure on /chaos to see compensation fire end-to-end.

Concurrency

Optimistic concurrency on real Postgres rows.

Click "Run race" to fire N concurrent inventory updates carrying the same xmin. Postgres lets exactly one win; the rest fail with 412 Precondition Failed. No lost updates.

Decisions on record

Why this is shaped the way it is.