An open-source single-seller ecommerce reference architecture and conference-talk anchor, built on the Critter Stack and exercising a disciplined spec-driven development pipeline.
CritterMart is a teaching reference architecture for event sourcing with the Critter Stack — JasperFx's family of .NET libraries including Wolverine (messaging and command handling) and Marten (event sourcing + document storage over PostgreSQL). The project's two purposes are explicit and in priority order:
- It is a working example for an Event Sourcing with Marten talk. CritterMart code and design artifacts carry the pedagogical weight, with the Place Order journey as the centerpiece.
- It is a sandbox for a disciplined Spec-Driven Development pipeline. Each vertical slice is produced via Context Mapping → Event Modeling workshop → OpenSpec proposal + sibling narrative → prompt → execute + retrospective, in that order, before any code is written. The pipeline doubles as the talk's section on what AI-assisted .NET development looks like in 2026.
CritterMart sits alongside other Critter-family reference architectures (CritterBids for auctions, CritterCab for ride-sharing, CritterSupply for broader ecommerce). It is deliberately the smallest of them in domain scope — single-seller storefront, four bounded contexts, one of which is stubbed — and the most deliberate about exposing its own design pipeline as a first-class artifact.
The codebase exists to be read, demonstrated, and learned from. It is not a complete ecommerce platform, and it is not meant for production use.
Ecommerce is a domain most developers intuitively understand — everyone has placed an order online. That familiarity lets the talk focus on how the system is built rather than getting bogged down explaining what it does. The single-seller variant is the smallest cut of ecommerce that still exercises the patterns event sourcing teaches well:
| Pattern | How CritterMart demonstrates it |
|---|---|
| Event sourcing | Stock per SKU (Inventory) and Cart + Order (Orders) are event-sourced; Catalog uses Marten's document store for contrast — "when CRUD is fine" |
| Process Manager via Handlers (PMvH) | The Order aggregate is the process manager — no separate saga state stream, no Wolverine.Saga base class; state guards on the stream enforce idempotency |
| Klefter translation-decision events | The Order stream commits external decisions (Inventory's grant/refusal, the stubbed payment provider's response) as first-class events on its own stream — audit trail by construction |
| Bruun temporal automation | CartAbandoned via scheduled CartActivityTimeout; OrderCancelled via OrderPaymentTimeout — both fire on clock conditions, with todo-list projections marked by the asterisk convention |
| Cross-BC integration via brokered messaging | Orders ↔ Inventory via Wolverine over RabbitMQ; no synchronous service-to-service HTTP |
| Mixed projection lifecycles | Most projections inline (StockLevelView, CartView, OrderStatusView); one async projection (CartAbandonmentReport) as a teaser for the rebuild-on-demand demo beat |
| OpenTelemetry cross-service tracing | Full Place Order trace spans Orders → RabbitMQ → Inventory and back — visible in the .NET Aspire dashboard |
| Spec-Driven Development pipeline | Vision → context map → workshop → OpenSpec proposal + narrative → prompt → execute + retrospective, with every step version-controlled |
The full slice list (17 round-one slices across the four BCs) lives in docs/workshops/001-crittermart-event-model.md.
CritterMart deploys as three separate .NET 10 services — Catalog, Inventory, and Orders — plus a stubbed Identity context. Cross-service communication is Wolverine over RabbitMQ; there is no synchronous service-to-service HTTP. Persistence is a shared PostgreSQL database with schema-per-service, accessed through Marten. .NET Aspire orchestrates the services, broker, and database locally, with OpenTelemetry tracing visible in the Aspire dashboard.
| Bounded Context | Responsibility | Storage | Status |
|---|---|---|---|
| Catalog | Products, prices, descriptions | Marten document store | Implemented — slices 1.1–1.3 (publish, browse, change price) |
| Inventory | Stock per SKU, reservations | Event-sourced (Marten) | Implemented — slices 2.1 receive, 2.2 reserve, 4.2 cross-BC reserve (responds to Orders' ReserveStock over RabbitMQ, all-or-nothing per order), 2.3 release on cancellation (responds to Orders' ReleaseStock, per-SKU idempotent) |
| Orders | Cart, Order (process manager), payment timeout, cart abandonment | Event-sourced (Marten) | Implemented — both aggregates complete. Order lifecycle: 4.1 placement, 4.2 / 4.5 cross-BC stock reservation + cancel-on-stock-failure, 4.3 / 4.4 stubbed payment authorization + confirmation, 4.6 cancel-on-payment-decline, 4.7 cancel-on-payment-timeout with the OrdersAwaitingPayment Bruun todo-list. Cart lifecycle: 3.1 add-to-cart, 3.2 / 3.3 cart edits (remove item, change quantity — SKU-keyed lines), 3.4 abandonment on inactivity (fire-and-check temporal automation, CartsAwaitingActivity todo-list, and the CartAbandonmentReport async-projection teaser — rebuild-on-demand, no daemon) |
| Identity (stubbed) | Customer identifier | Hardcoded in frontend (round one) | Stubbed by design; deployed-service promotion queued in vision.md § Long road |
Catalog has no BC-level integration with Inventory or Orders in round one — product fields cross only via the frontend, which snapshots them into Cart commands at add-to-cart time. The Orders ↔ Inventory relationship is Customer-Supplier (Inventory is the supplier, Orders the customer). Identity's relationship to the three deployed services is Conformist with no active wire integration. See docs/context-map/README.md for the full topology, integration relationships table, and round-one stubs.
| Layer | Choice |
|---|---|
| Runtime | C# 14 / .NET 10 |
| Messaging | Wolverine 6+ over RabbitMQ |
| Persistence | Marten 9+ on PostgreSQL (shared database, schema-per-service) |
| HTTP | Wolverine.Http (per service; no BFF for round one) |
| Testing | Alba (integration), xUnit + Shouldly (unit) |
| Orchestration | .NET Aspire |
| Observability | OpenTelemetry (traces surface in the Aspire dashboard) |
| Frontend | TBD for round one |
The full tech-stack rationale lives in the round-one ADRs at docs/decisions/ and is summarized terse-form in docs/rules/structural-constraints.md.
The talk — Event Sourcing with Marten — is the project's centerpiece and the destination for round one. 50-minute slot with a 30-minute cut, no live coding. The Place Order journey is rendered as a full OpenTelemetry-traced storyboard: a customer taps "Place Order," the trace fans across Orders → RabbitMQ → Inventory and back, the Order aggregate's process manager logic gates on StockReserved and PaymentAuthorized, and the order either confirms or cancels through one of three failure paths (insufficient stock, payment declined, or payment timeout).
What the demo shows:
- A working OpenTelemetry trace spanning multiple services, end-to-end in the Aspire dashboard
- The Order aggregate acting as its own process manager (no separate saga stream)
- Klefter translation-decision events capturing external decisions as local stream facts
- Bruun temporal automation handling
CartAbandonedandOrderPaymentTimeout - One async projection (
CartAbandonmentReport) demonstrating rebuild-on-demand - The full design pipeline visible in
docs/— vision through workshop through slice spec through implementation prompt
The pipeline itself doubles as the talk's section on what AI-assisted .NET development looks like in 2026: each artifact is a durable record of intent, and any session-runner — human or AI — can pick up where the last one left off.
Status note. CritterMart's round-one modeled implementation set is complete — every slice in Workshop 001 has shipped. The design artifact suite (vision, context map, workshop, ADRs, rules, folder READMEs, skills, prompts, retrospectives) is complete, and
src/holds three Wolverine services (Catalog, Inventory, Orders), theCritterMart.AppHostAspire orchestrator,CritterMart.ServiceDefaults, andCritterMart.Contracts(the cross-BC published language). Catalog (1.1–1.3) and Inventory (2.1–2.3) are in; the Orders BC's Order lifecycle is complete (4.1 place-order, 4.2/4.5 cross-BC stock reservation + cancel-on-stock-failure — the project's first live RabbitMQ traffic — 4.3/4.4 stubbed payment authorization + confirmation, 4.6 cancel-on-payment-decline with cross-BC stock release, and 4.7 cancel-on-payment-timeout — the project's first temporal automation; every placed order reachesconfirmedorcancelled); and the Cart lifecycle is complete (3.1 add-to-cart, 3.2/3.3 cart edits with SKU-keyed lines, and 3.4 abandonment on inactivity — fire-and-check temporal automation plus theCartAbandonmentReportasync-projection teaser, rebuild-on-demand with no daemon per ADR 008; every cart reachesCartCheckedOutorCartAbandoned). What comes next — frontend, talk storyboard assets, round-two modeling — is a vision-level decision, not a remaining slice. The instructions below reflect the working local-development workflow.
- .NET 10 SDK
- Docker Desktop (or an equivalent OCI runtime) — Aspire orchestrates PostgreSQL and RabbitMQ as containers
- An IDE with C# tooling (JetBrains Rider, Visual Studio, or VS Code with the C# Dev Kit)
CritterMart's design pipeline assumes an AI session-runner (Claude Code, in author practice). A fresh machine needs the following before a session can pick up where the last one left off:
- Claude Code (or another agent capable of reading
CLAUDE.md, the local skills directories, and thedocs/artifacts). The repo commits a shared.claude/settings.jsonwith a permissions allow-list for the project's Docker, dotnet, andghtooling so a fresh clone starts with a sensible baseline; personal preferences (model, output style, effort level) belong in.claude/settings.local.json(per-machine, gitignored) or~/.claude/settings.json(user-global). - GitHub CLI (
gh) — the session pipeline opens PRs viagh pr createand reads PR state viagh api. Authenticate once per machine withgh auth login. ctx7—npx ctx7@latestfetches current docs for any library the session calls into; preferred over web search for API and configuration questions, and used by Erik's global Claude Code rules.- JasperFx Critter Stack ai-skills library, installed per the upstream instructions at ai-skills.jasperfx.net/install. These are the knowledge skills (Marten projections, Wolverine handlers, Polecat setup, EF Core integration, testing patterns) that
CLAUDE.md's routing layer defers to for component-scoped patterns. - Matt Pocock workflow skills, installed via
npx skills@latest add mattpocock/skills(source: github.com/mattpocock/skills). These are the workflow skills (grill-me,diagnose,handoff,tdd,prototype,improve-codebase-architecture, etc.) that complement the JasperFx knowledge skills.
Both skill collections install globally (to ~/.claude/skills/ and a universal mirror) and are discovered by Claude Code at session start with no per-project registration. Each machine installs them independently — they are not vendored into this repo. The two installers have independent upgrade cycles; do not try to consolidate them.
.NET Aspire boots PostgreSQL, RabbitMQ, and the services together:
dotnet run --project src/CritterMart.AppHost --launch-profile httpThe Aspire dashboard (default http://localhost:15090) surfaces OpenTelemetry traces across services; the cross-service Place Order trace fills in as the Order journey (4.x) lands. The canonical entry point for the design pipeline remains CLAUDE.md.
CritterMart/
├── docs/ # The heart of the project right now
│ ├── vision.md # Canonical source of truth — purpose, BCs, non-goals
│ ├── context-map/ # Cross-BC DDD relationships and topology
│ ├── workshops/ # Event Modeling output (round-one rolled-up model)
│ ├── narratives/ # NDD-informed journey specs (per slice)
│ ├── decisions/ # Round-one ADRs
│ ├── rules/ # AI-optimized structural constraints
│ ├── skills/ # Component-scoped patterns (event-modeling skill local)
│ ├── prompts/ # Per-session intent records, frozen at session start
│ ├── retrospectives/ # Per-session outcome records, spec-delta closure
│ └── research/ # Spikes and exploratory work
├── openspec/ # OpenSpec workspace (peer to docs/) — CLI-managed
│ ├── changes/ # Per-slice changes (proposal.md + SHALL specs); archive/ holds shipped changes
│ └── specs/ # Main specs, synced from a change on archive
├── src/ # Catalog, Inventory, Orders services + AppHost + ServiceDefaults + Contracts (cross-BC published language)
├── tests/ # Per-service test projects + CrossBc.Tests (two-host cross-BC smoke)
├── CLAUDE.md # AI development entry point and pipeline overview
├── LICENSE # MIT
└── README.md # You are here
| Document | Purpose |
|---|---|
docs/vision.md |
Canonical source of truth — purpose, bounded contexts, non-goals |
docs/context-map/README.md |
Cross-BC DDD relationships and integration topology |
docs/workshops/ |
Event Modeling output (round-one rolled-up model) |
docs/narratives/ |
NDD-informed journey specs (per slice) |
openspec/changes/ |
OpenSpec proposals + per-capability SHALL specs (per slice, CLI-managed) |
docs/decisions/ |
Round-one ADRs (indexed in the folder README) |
docs/rules/structural-constraints.md |
AI-optimized terse imperative list of architectural constraints |
docs/skills/ |
Component-scoped patterns (one current local skill: event-modeling; others defer to upstream) |
docs/prompts/ |
Per-session intent records |
docs/retrospectives/ |
Per-session outcome records |
CLAUDE.md |
AI development entry point, pipeline overview, and routing layer |
CritterMart's second deliberate purpose is exercising a disciplined SDD pipeline as a teaching artifact in its own right. Every vertical slice flows through:
- Context Mapping (cross-BC DDD vocabulary) —
docs/context-map/README.md, amended as new BCs appear - Event Modeling workshop (one rolled-up artifact for round one) —
docs/workshops/001-crittermart-event-model.md - OpenSpec proposal + Narrative siblings (per slice, must agree) —
openspec/changes/{change}/proposal.md+docs/narratives/NNN-{actor}-{journey}.md - Prompt (per session, frozen at session start) —
docs/prompts/README.md - Execute + Retrospective (per session, retro before PR opens) —
docs/retrospectives/README.md
Strong operating disciplines hold the pipeline together: one prompt = one session = one PR; no opportunistic edits; spec-delta closure loop (every prompt names its spec delta, every retro confirms whether it landed); design-return cadence after 2–3 implementation PRs against a single bounded context. Full treatment in CLAUDE.md.
In scope (per docs/vision.md success criteria):
- Browse the catalog and view a product
- Add items to a cart, with cart abandonment events captured from day one
- Check out and start an order
- Order placement coordinating stock reservation and stubbed payment authorization across BCs
- Order completion when both gates close, or cancellation on stock failure / payment decline / payment timeout
- A working OpenTelemetry trace of the Place Order journey, visible in the Aspire dashboard
- Per-slice design artifacts (OpenSpec proposal + sibling narrative)
- One async projection (
CartAbandonmentReport) as a teaser for the rebuild-on-demand demo beat
Deliberately out (per docs/vision.md § What this deliberately is not):
- No vendor portal, vendor identity, marketplace listings, or multi-channel sales
- No backoffice or admin UI
- No real payment integration (payment is stubbed inside Orders)
- No returns, no promotions, no shipping rate calculations, no real-time storefront updates
- No Polecat for round one; Identity is stubbed (customer ID hardcoded in the frontend)
- No live coding in the demo
Many of the cuts are explicit candidates for future rounds, tracked in docs/vision.md § Long road and docs/context-map/README.md § Long road.
CritterMart defers to the JasperFx ai-skills library for generic Critter Stack patterns (Wolverine, Marten, Polecat). Local skills under docs/skills/ are authored only when a CritterMart-specific convention diverges from upstream, or when a project-specific methodology needs its own home (the in-repo event-modeling skill is the current example). The project does not duplicate upstream content. See docs/skills/README.md for the layering rationale.
CritterMart's session-driven workflow is documented in docs/prompts/README.md and docs/retrospectives/README.md. Before contributing:
- Read
CLAUDE.mdfor the pipeline overview, architectural non-negotiables, and operating disciplines. - For implementation work, follow the one-prompt = one-session = one-PR rhythm: author a prompt that names the spec delta, execute, write the retro confirming closure (or honest non-closure), open a PR carrying all three.
- Branches follow
{type}/{slug}matching the conventional-commit prefix (e.g.,tidy/docs-folder-readmespaired withtidy: docs — add folder READMEs for routing-layer narrowing). - All session intent and outcomes are version-controlled in
docs/prompts/anddocs/retrospectives/.
- Blog: event-sourcing.dev
- Wolverine: wolverine.netlify.app
- Marten: martendb.io
- JasperFx: github.com/jasperfx
- Tools: JetBrains Rider, DataGrip
Erik "Faelor" Shafer