A microservices-based e-commerce platform built with .NET 10, Blazor, and .NET Aspire.
NextAurora demonstrates a production-style distributed system with event-driven architecture, CQRS, domain-driven design, and gRPC for inter-service communication.
Live demo: catalog-api-demo.fly.dev/scalar/v1 — CatalogService deployed to Fly.io with an interactive Scalar API explorer. Try
GET /api/v1/productsfor the 7 seeded products. Auto-stops when idle, so the first request after a quiet period takes ~10s to wake the machine. Scope: Catalog only — the full Order → Payment → Shipping → Notification saga runs locally via Aspire (see Getting Started).
About this repo:
- Monorepo, single architectural shape. All five services use Vertical Slice Architecture — single Web SDK project,
Features/<UseCase>.csco-locating command/query + validator + handler, aggregates inDomain/. CatalogService originally used Clean Architecture (4 projects); it was collapsed to VSA in the simplicity refactor once the layer split stopped earning its keep at this scale. Handlers takeDbContextdirectly — noIFooRepositorywrappers — and integration tests with Testcontainers replace mocked-repository unit tests. See CLAUDE.md "Project Structure" for the promotion signal (5+ aggregates with cross-cutting rules → consider Clean). The original shape is preserved at thev1-repository-patterntag —git checkout v1-repository-patternbrowses a textbook EF Repository pattern across all 5 services for comparison.- Two database engines on purpose. CatalogService + ShippingService run on PostgreSQL (Npgsql); OrderService + PaymentService run on SQL Server (Microsoft.Data.SqlClient). NotificationService is stateless. The split exercises both EF Core providers and the per-provider primitives the architecture leans on: Postgres
xmin(system column, no schema change) vs SQL Serverrowversion(real column, requires migration) for optimistic-concurrency tokens; Wolverine'sPersistMessagesWithPostgresqlvsPersistMessagesWithSqlServerfor the transactional outbox;DistributedLock.SqlServer(sp_getapplock) for the PaymentRecoveryJob sweeper.
How it was built — AI-assisted, multi-model review, verification at every layer.
- Two AI reviewers, not one. Claude Code (Opus 4.7) is the primary pair-programmer — reads
CLAUDE.mdplus the.claude/folder beside it (agents, skills, slash commands, hook scripts, hook + permission wiring) every session, and a persistent project memory. GitHub Copilot (GPT-5) sits in-editor for second-opinion diff review, with project conventions encoded in.github/copilot-instructions.md. Disagreement between the two is treated as a signal to dig deeper, not pick the louder voice. The principle is not "AI wrote it" — it's two models + a human author + automated checks all sign off before merge. The working loop: implement → run unit + integration tests → cross-model review → fix → commit.- Continuous Rule Encoding — the compounding feedback loop. Every meaningful finding (CodeRabbit catch, test failure, architecture-reviewer agent flag, manual code review, prod incident, even community articles audited via
/article-audit) gets the question: "could the next person repeat this?" If yes, the rule gets written down at the right tier of an enforcement spectrum — Convention (CLAUDE.md +.claude/), PR-review automation (.coderabbit.yaml+ the architecture-reviewer agent), or Mechanical (build-fail / hook-block / CI-grep). Rules move down the spectrum as they prove their value — the looser the tier, the more you rely on noticing, and noticing always degrades first. CLAUDE.md itself stays lean (~300 lines; CI-guarded soft cap at 400, hard fail at 500); deep dives live indocs/and thedotnet-performanceskill. Six disciplines sit alongside the surfaces — cross-reference convention, file-move, doc-and-diagram, lean-CLAUDE.md, presence-in-the-loop, continue-is-the-verb — the first four with mechanical floors (hooks, CI guards, size budget), the last two convention-tier (judgment calls about presence and stopping). New features run through/feature-spec, which opens with a value gate (who needs this, would we still build it at engineering-time cost, who owns saying no), then drafts a structured handoff (goal + acceptance + upstream dependencies + auto-referenced CLAUDE.md constraints) and closes with a hole-test (imagine handing this to someone not in your head — where would they have to guess?). Full mechanics + the 5 encoding surfaces + the disciplines catalog:docs/dev-loop.md; scaffolding diagram:docs/dev-loop-scaffolding.svg.- Verification at every layer. Build:
TreatWarningsAsErrors+ four build-time analyzers (Meziantou, SonarAnalyzer.CSharp, Roslynator, BannedApiAnalyzers — the last rejects concrete concurrency hazards at compile). Tests: 100+ unit + integration tests, with integration slices for all four DB-touching services via Testcontainers — Catalog (Postgres + Redis), Order (SQL Server + stubbed Wolverine transport), Payment (SQL Server), Shipping (Postgres). Each slice runs as its own step in CI so a single-slice failure doesn't mask the rest. CI: GitHub Actions runs build + tests + Coverlet/Cobertura coverage on every PR. Security: CodeQL on every push + weekly schedule. Dependencies: Dependabot weekly NuGet bumps (grouped per ecosystem). Local dev: Aspire orchestrates Postgres / SQL Server / RabbitMQ / Redis / Keycloak in Docker so integration issues surface before push.- Testing strategy was decided upfront, not retrofitted. Unit tests cover handlers + domain rules with mocked infrastructure (NSubstitute, FakeTimeProvider). Integration tests use live containers to prove what mocks can't: EF migrations apply cleanly to fresh DBs, HybridCache actually invalidates on write, the
xmin/RowVersionconcurrency tokens actually fire under racing writes, Wolverine's transactional outbox + saga handlers actually persist and dispatch. Cross-service E2E over a live RabbitMQ wire is tracked as deferred in STATUS.md. Open issues and other deferred cleanups live there too.
Full system in one view — services, RabbitMQ messaging topology, databases, and the 10-step order-placement saga. Click to view full-size.
Drill down into specific subsystems: service request lifecycle · HybridCache flow · transactional outbox · EF Core read and write · EF Core migrations — all six diagrams in the Reference diagrams section below.
+-----------------------------------------------------+
| FRONTEND LAYER |
| |
| Storefront SellerPortal |
| (Blazor WASM) (scaffold) |
+-------+--------------------+-------------------------+
| REST | REST
v v
+-----------------------------------------------------+
| API LAYER |
| |
| +--------------+ +---------------+ |
| | CatalogSvc |<---| OrderSvc | |
| | (PostgreSQL) |gRPC| (SQL Server) | |
| +--------------+ +------+--------+ |
| | |
| +--------------+ +------+--------+ +--------+ |
| | PaymentSvc | | ShippingSvc | |Notif- | |
| | (SQL Server) | | (PostgreSQL) | |ication | |
| +--------------+ +---------------+ |Svc | |
| +--------+ |
+-----------------------------------------------------+
| | |
v v v
+-----------------------------------------------------+
| MESSAGING LAYER |
| |
| Async Messaging (Fanout Exchanges & Queues) |
| RabbitMQ via Wolverine — same broker in |
| every environment (local dev, CI, deployment) |
| |
| Fanout exchanges (queue per consumer): |
| order-events -----> PaymentSvc, NotificationSvc |
| payment-events ---> OrderSvc, ShippingSvc, |
| NotificationSvc |
| shipping-events --> OrderSvc, NotificationSvc |
+-----------------------------------------------------+
| | |
v v v
+-----------------------------------------------------+
| INFRASTRUCTURE LAYER |
| |
| PostgreSQL SQL Server Redis App Insights |
| (catalog, (orders, (cache) (telemetry) |
| shipping) payments) |
+-----------------------------------------------------+
Orchestrated by .NET Aspire (service discovery, health checks, OpenTelemetry)
| Service | Database | Purpose |
|---|---|---|
| CatalogService | PostgreSQL | Product catalog, categories, search |
| OrderService | SQL Server | Order placement, lifecycle management |
| PaymentService | SQL Server | Payment processing (Stripe integration) |
| ShippingService | PostgreSQL | Shipment creation, tracking |
| NotificationService | Stateless | Email notifications (order confirmations, shipping updates) |
| Storefront | - | Customer-facing Blazor WASM SPA (scaffold) |
| SellerPortal | - | Merchant dashboard (scaffold — currently a static-file ASP.NET Core host, no UI framework chosen) |
- .NET 10 / C# 13
- ASP.NET Core Minimal APIs
- Blazor WebAssembly (Storefront, scaffolded — no business logic yet)
- ASP.NET Core static-file host (SellerPortal scaffold — no UI framework chosen yet, currently serves a placeholder
index.html) - Entity Framework Core 10 (PostgreSQL + SQL Server) with EF migrations
- RabbitMQ for async event-driven messaging (fanout exchanges + queue per consumer, via Wolverine's
WolverineFx.RabbitMQtransport) - Wolverine for command/query dispatch, message handling, and the transactional outbox
- gRPC for synchronous inter-service communication
- Keycloak + JWT Bearer for authentication and authorization
- Asp.Versioning for URL-segment API versioning (
/api/v1/...) - .NET Aspire for orchestration, service discovery, and observability
- OpenTelemetry for distributed tracing, metrics, and logging
- HybridCache (.NET 10) for two-tier read caching — in-process MemoryCache (L1) + Redis (L2), with stampede protection and tag-based invalidation
- .NET 10 SDK
- Docker Desktop — running, not just installed (Aspire spins up Postgres/SQL Server/RabbitMQ/Keycloak/Redis as containers)
- Aspire CLI
- ASP.NET Core dev certificate — required by the Aspire dashboard's HTTPS endpoint. One-time per machine:
dotnet tool install --global aspire.cli
dotnet dev-certs https
dotnet dev-certs https --trust # prompts for keychain password on macOSGotcha: if
dotnet dev-certsprints "HTTPS development certificate operations are disabled in this environment. Your application will run on HTTP for local development.", the env varDOTNET_GENERATE_ASPNET_CERTIFICATE=falseis set somewhere in your shell init (commonly~/.zshrc). Find and remove it:grep -rn "DOTNET_GENERATE_ASPNET_CERTIFICATE" ~/.zshrc ~/.zprofile ~/.zshenv ~/.bashrc ~/.bash_profile ~/.profile, delete the matching line, open a fresh terminal, then re-run the dev-certs commands.
- Clone the repository
git clone <repo-url>
cd NextAurora- Restore dependencies
dotnet restore- Run with Aspire
dotnet run --project NextAurora.AppHostThis starts all services, databases (PostgreSQL, SQL Server), Redis, and RabbitMQ (management UI on :15672) in Docker containers. The Aspire dashboard opens automatically showing all services, health status, logs, and distributed traces.
- Access the applications
| Application | URL |
|---|---|
| Aspire Dashboard | https://localhost:17225 |
| Storefront | Shown in Aspire Dashboard |
| SellerPortal | Shown in Aspire Dashboard |
| CatalogService API | Shown in Aspire Dashboard |
| OrderService API | Shown in Aspire Dashboard |
-
Verify it's working — once every resource in the Aspire dashboard reaches
Running(first boot is slow; SQL Server takes 60–90s to be healthy on cold runs):- Click
catalog-servicein the Resources tab and open its/scalar/v1URL - Run
GET /api/v1/products— you should see 7 seeded products - For the full saga walk (auth → place order → payment → ship → notify): see scripts/smoke-test.sh
- Click
🔒 = requires JWT Bearer authentication. Pagination params apply to list endpoints (?page=1&pageSize=50, server cap 100).
API versioning: URL-segment versioning via Asp.Versioning.Http. The version is required in the route — /api/v1/.... Default version is 1.0; unversioned URLs (/api/products) return 400. Versioned endpoints appear in OpenAPI under group v1.
GET /api/v1/products?page=&pageSize=- List products (paginated)GET /api/v1/products/{id}- Get product by IDGET /api/v1/products/search?query=&page=&pageSize=- Search products (rate-limited, paginated)- 🔒
POST /api/v1/products- Create a product - 🔒
PUT /api/v1/products/{id}- Update a product
- 🔒
POST /api/v1/orders- Place an order (buyerId in command must match JWTsub) - 🔒
GET /api/v1/orders/{id}- Get order by ID - 🔒
GET /api/v1/orders/buyer/{buyerId}?page=&pageSize=- Get orders by buyer (paginated; route buyerId must match JWTsub)
- 🔒
POST /api/v1/payments/process- Process a payment (rate-limited)
- 🔒
GET /api/v1/shipments/order/{orderId}- Get shipment by order ID
NextAurora/
NextAurora.AppHost/ # Aspire orchestrator
NextAurora.ServiceDefaults/ # Shared OpenTelemetry, health checks, resilience
NextAurora.Contracts/ # Shared events, commands, DTOs
CatalogService/ # VSA — largest service (2 aggregates, gRPC server, HybridCache)
Features/ # GetProductById.cs, UpdateProduct.cs, ReserveStock.cs, etc.
Domain/ # Product, Category aggregates; IProductCache port
Infrastructure/ # EF Core (Postgres + Migrations), HybridProductCache, DI
Endpoints/ # Minimal-API HTTP surface
Grpc/ # gRPC server (CatalogGrpcService — the peer for OrderService's client)
Protos/catalog.proto # Shared proto contract
Program.cs # Composition root
OrderService/ # VSA
Features/ # One file per use case: PlaceOrder.cs, GetOrderById.cs, saga handlers
Domain/ # Order aggregate, OrderLine, ports
Infrastructure/ # EF Core (Data/ + Migrations/), gRPC client to Catalog
Endpoints/
Program.cs
PaymentService/ # VSA
Features/ # ProcessPayment.cs (command + validator + handler), OrderPlacedHandler.cs
Domain/ # Payment aggregate, ports (incl. IPaymentGateway)
Infrastructure/ # EF Core, Stripe ACL (Gateway/), Wolverine adapter, recovery job
Endpoints/
Program.cs
ShippingService/ # VSA
Features/ # CreateShipment.cs, GetShipmentByOrder.cs, PaymentCompletedHandler.cs
Domain/ # Shipment aggregate, TrackingEvent, ports
Infrastructure/ # EF Core, Wolverine adapter
Endpoints/
Program.cs
NotificationService/ # VSA — smallest service, stateless
Features/ # SendNotification.cs (record + port + handler), NotificationEventHandlers.cs
Infrastructure/ # ConsoleNotificationSender, DI
Program.cs
Storefront/ # Blazor WASM customer app (scaffold)
SellerPortal/ # ASP.NET Core static-file host scaffold (UI framework TBD)
One shape across all five services. Vertical Slice Architecture everywhere — features
are co-located by use case (Features/<UseCase>.cs containing command/query + validator +
handler), aggregates live in Domain/, and each service is a single Web SDK project.
CatalogService previously used Clean Architecture (4 projects) but was collapsed to VSA in
the simplicity refactor — at ~2k LOC and 2 aggregates the layer split wasn't earning its
keep, and one consistent shape is a stronger story than "we calibrate per service."
See CLAUDE.md "Project Structure" for the promotion signal
(when 5+ aggregates with cross-cutting domain rules emerge, consider Clean Architecture).
The order lifecycle is fully automated through event-driven choreography:
Customer places order
-> OrderService creates order (validates products via gRPC)
-> Publishes OrderPlacedEvent
PaymentService receives OrderPlacedEvent
-> Processes payment via Stripe gateway
-> Publishes PaymentCompletedEvent (or PaymentFailedEvent)
OrderService receives PaymentCompletedEvent
-> Marks order as Paid
ShippingService receives PaymentCompletedEvent
-> Creates shipment, assigns carrier and tracking number
-> Publishes ShipmentDispatchedEvent
OrderService receives ShipmentDispatchedEvent
-> Marks order as Shipped
NotificationService receives OrderPlacedEvent
-> Sends "Order Received" notification
NotificationService receives ShipmentDispatchedEvent
-> Sends "Order Shipped" notification with tracking info
Every request and RabbitMQ message carries three identifiers through the entire chain:
| Field | Source | Propagated Via |
|---|---|---|
CorrelationId |
X-Correlation-Id header (generated if absent) |
Activity baggage → message envelope headers |
UserId |
JWT sub claim |
Activity baggage → message envelope headers |
SessionId |
X-Session-Id header |
Activity baggage → message envelope headers |
These appear on every structured log line in every service, making it possible to search for a single CorrelationId and see the complete transaction timeline across all five services.
Key components:
CorrelationIdMiddleware(ServiceDefaults) — HTTP entry point; extracts all three IDs from request headers and JWT claimsContextPropagationMiddleware— Wolverine middleware on the incoming side; restores the three IDs from envelope headers into the logger scope before each handler runsOutgoingContextMiddleware— Wolverine middleware on the outgoing side; stamps the three IDs onto outgoing message envelopesWolverineEventPublisher— thin pass-through toIMessageBus.PublishAsyncso domain code stays infrastructure-agnostic
Order, Payment, and Shipping run Wolverine's transactional outbox: outgoing events persist to a wolverine schema in each service's database in the same DB transaction as the entity write, then dispatch to RabbitMQ via a background flush. See docs/context-propagation.md and docs/performance-and-data-correctness.md for full details.
Two harnesses, both opt-in.
dotnet run -c Release --project benchmarks/NextAurora.Benchmarks
# Or filter to a single benchmark class:
dotnet run -c Release --project benchmarks/NextAurora.Benchmarks -- --filter '*OrderFactory*'Always run in Release — Debug numbers are not representative. Currently includes OrderFactoryBenchmarks (Order aggregate creation with 1/5/25 line counts). Add new benchmarks under benchmarks/NextAurora.Benchmarks/ following the same pattern.
brew install k6 # macOS; see https://k6.io/docs/getting-started/installation/ for others
# AppHost must be running. CATALOG_URL is the same value smoke-test.sh uses.
CATALOG_URL=https://localhost:XXXXX k6 run scripts/k6/smoke.jsCurrently includes smoke.js (1 VU for 30s with p95 < 500ms / error rate < 1% thresholds). See scripts/k6/README.md.
The project enforces code quality standards from day one:
- Directory.Build.props - Centralized build settings,
TreatWarningsAsErrors, static analyzers (Meziantou, SonarAnalyzer, Roslynator) - Directory.Packages.props - Central Package Management for consistent NuGet versions
- .editorconfig - Coding standards and naming conventions
- GitHub Actions - CI pipeline for build and test on every push/PR
JWT Bearer authentication is configured in NextAurora.ServiceDefaults and applied to every service. Identity is provided by Keycloak, which runs as an Aspire-managed container with the nextaurora-realm imported from realms/nextaurora-realm.json.
- JWT validation — issuer, audience, lifetime, and signing all validated via
AddJwtBearer(Authentication:Authorityconfig falls back toKeycloak:Url). - Claim mapping —
preferred_usernameis the name claim,realm_access.rolesis the role claim. - Endpoint protection —
.RequireAuthorization()on every state-changing endpoint and the buyer-scoped read endpoints (Catalog write endpoints, all of/api/v1/orders,/api/v1/payments/process, all of/api/v1/shipments). Public read endpoints (GET /api/v1/products) remain anonymous. - Buyer-scope enforcement — endpoints that operate on a buyer's data (e.g.
GET /api/v1/orders/buyer/{buyerId},POST /api/v1/orders) verify the JWTsubclaim matches the route/body buyer ID and return 403 otherwise.
If Keycloak isn't configured (no Authentication:Authority and no Keycloak:Url), ServiceDefaults registers no-op auth services — UseAuthentication doesn't crash, but every .RequireAuthorization()-protected endpoint returns 401.
- Input Validation - FluentValidation validators on all commands, enforced via Wolverine's
UseFluentValidation()pipeline policy (validators run before handlers; failures throwValidationException) - Domain Invariants - All entities enforce business rules in factory methods (guard clauses for invalid state)
- Optimistic Concurrency -
xmin(Postgres) /RowVersion(SQL Server) tokens on every aggregate;DbUpdateConcurrencyExceptionbecomes 409 Conflict on the HTTP path and triggers Wolverine retry on the message path - Transactional Outbox - Wolverine outbox in Order, Payment, Shipping; entity writes and event publishes commit atomically
- Rate Limiting - Fixed-window limiters on
/api/v1/products/search(search) and/api/v1/payments/process(payments) - Global Exception Handling - ProblemDetails responses with trace IDs; internal details never leaked to clients
- Encapsulated Aggregates - Collections exposed as
IReadOnlyList<T>with private backing fields - HTTPS Redirection - Enforced in production environments
- Server-Side Pricing - Order totals calculated from catalog data, not client-submitted prices
- Pagination Caps - List endpoints take
page/pageSize(default 50, server-side max 100)
| Pattern | Technology | Use Case |
|---|---|---|
| Event-Driven (Async) | RabbitMQ (via Wolverine) | Order workflows, payment processing, shipping, notifications |
| gRPC (Sync) | Protocol Buffers | Product validation during order placement |
| REST (External) | ASP.NET Core Minimal APIs | Frontend-to-service communication |
NextAurora uses Wolverine as the in-process dispatcher (commands, queries, event handlers). One subtlety surprises everyone the first time they write an integration test:
opts.Discoveryis NOTAddScoped. Wolverine builds its own internal handler-type map forIMessageBus.InvokeAsync<T>()dispatch — it constructs handlers itself viaIServiceScopeFactoryand never asksIServiceCollectionfor the handler type. SoserviceProvider.GetRequiredService<MyHandler>()throwsInvalidOperationException: No service for type 'MyHandler' has been registeredunless you also register the handler concretely.
Production code is unaffected — endpoints go through IMessageBus:
orders.MapGet("/{id:guid}", async (Guid id, IMessageBus bus, CancellationToken ct) =>
await bus.InvokeAsync<OrderSummaryDto?>(new GetOrderByIdQuery(id), ct));But read-handler integration tests typically skip the HTTP/auth layer and resolve the handler directly to assert the EF projection SQL. Those tests need an explicit registration:
// OrderService/Infrastructure/DependencyInjection.cs
services.AddScoped<GetOrderByIdHandler>();
services.AddScoped<GetOrdersByBuyerHandler>();AddScoped<T>() (single-type overload) registers the concrete type as both service-key and implementation — scoped lifetime, matches DbContext, no interface needed.
This is documented as a hard rule in CLAUDE.md "Communication Patterns → Wolverine handler discovery is NOT DI registration", checked by CodeRabbit in .coderabbit.yaml on every PR, and explained with the full mechanism in docs/how-it-works.md "Two containers, not one".
| Guide | Description |
|---|---|
| How It Works | Developer walkthrough — VSA layout, CQRS via Wolverine, request lifecycle, outbox, event flow, testing |
| Architecture | Service diagrams, communication matrix, domain model, design patterns |
| Performance & Data Correctness | Hard rules + decisions: AsNoTracking strategy, optimistic concurrency tokens, Wolverine outbox, HybridCache, Dapper escape hatch |
| EF Core: Spec & Practice | Reference guide: how we use EF Core, every decision + trade-off + code example, from concurrency tokens to the Dapper escape hatch |
| Modern .NET 10 / C# 13 Features in Use | Reference of the modern .NET features actively used in NextAurora — HybridCache, primary constructors, collection expressions, Asp.Versioning.Http, IExceptionHandler, Wolverine over MediatR+MassTransit, etc. Anchored in file:line. |
| Project Decisions — API, Libraries, Architecture | Reference guide: cross-cutting decisions — Minimal APIs, URL versioning, Wolverine vs MediatR, HybridCache, Keycloak, observability, every library pick + alternative considered |
| VSA vs. Clean Architecture | Portable decision guide (reusable across systems): the dependency rule vs the 4-project structure, the enforcement spectrum (convention → architecture tests → project split), how Testcontainers shifted the testing calculus, the duplication tradeoff, when to use which — NextAurora's VSA-everywhere choice as worked example |
| Messaging Transport Selection | Portable decision guide (reusable across systems): when to use Redis Pub/Sub vs Redis Streams vs RabbitMQ vs cloud brokers (ASB, AWS SNS+SQS) vs Kafka/Event Hubs/Kinesis — decision axes, comparison matrix, common mistakes, NextAurora's RabbitMQ choice as worked example |
| Observability | Correlation/user/session ID propagation, distributed tracing, Wolverine handler logging, DLQ handling, metrics |
| Event Replay | Wolverine outbox state, where to inspect outgoing/dead-letter envelopes, IMessageStore API |
| Business Requirements | Functional requirements, implementation status, business processes, glossary |
| Demo Deployment (recipe) | One-time setup checklist for deploying CatalogService to Fly.io or AWS App Runner with Scalar exposed |
| Demo Deployment (story) | Narrative of what we actually did to deploy live at https://catalog-api-demo.fly.dev — decisions, gotchas, EF migration trade-offs |
| Project Status | Cross-session entry point — recently landed, next, open issues |
Six diagrams break the system down — one concept per visual. Each is self-contained: title + numbered steps + side annotations explaining the what and why. Click any image for full-size. Editable .excalidraw sources live alongside each .svg in docs/.
5 services, RabbitMQ messaging topology, databases, 10-step order-placement saga, cache + outbox callouts — embedded above in the overview.
Generic write-command lifecycle — every step from HTTP POST → CorrelationIdMiddleware → versioned routing → auth + buyer-scope check → Wolverine pipeline (validation / context propagation / AutoApplyTransactions) → handler → SaveChanges → 201/202. Plus the GlobalExceptionHandler error-routing sidebar.
Catalog's cache flow: GetOrLoadAsync → L1 (μs) → L2 (ms) → factory (once under stampede) → store both tiers. Plus the write/invalidate path (InvalidateAsync ordering matters) and the multi-replica L1 caveat (HybridCache 10.x has no backplane).
The load-bearing reliability mechanism behind every cross-service event. Entity write + outbox-row write committed in ONE transaction (visual: dotted "TRANSACTION BOUNDARY" wrapping both). Background dispatcher → RabbitMQ → delete envelope. All failure modes spelled out.
Side-by-side READ (LINQ → expression tree → provider SQL → DataReader → DTO, no tracker) and WRITE (load tracked → mutate → SaveChanges → UPDATE WHERE Id AND xmin/RowVersion → 0-rows branch → DbUpdateConcurrencyException → 409 or Wolverine retry). With Postgres xmin vs SQL Server RowVersion callout.
Dev round-trip (dotnet ef migrations add → IDesignTimeDbContextFactory → snapshot diff → emitted classes → MigrateDatabaseAsync at startup) vs prod (separate CI pre-deploy step — never in-process at startup, because replicas race). Plus the immutable-once-applied rule with the multi-step destructive-change recipe.
MIT — Copyright (c) 2026 Joshua Dell. Free to use, modify, and redistribute with attribution.