Skip to content

erikshafer/crittermart

Repository files navigation

CritterMart

.NET PostgreSQL Wolverine RabbitMQ License: MIT

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.


What Is CritterMart?

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:

  1. 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.
  2. 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.


Why a Single-Seller Storefront?

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.


Architecture

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 Contexts

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.


Tech Stack

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

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 CartAbandoned and OrderPaymentTimeout
  • 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.


Getting Started

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), the CritterMart.AppHost Aspire orchestrator, CritterMart.ServiceDefaults, and CritterMart.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 reaches confirmed or cancelled); 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 the CartAbandonmentReport async-projection teaser, rebuild-on-demand with no daemon per ADR 008; every cart reaches CartCheckedOut or CartAbandoned). 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.

Prerequisites

  • .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)

AI session tooling

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 the docs/ artifacts). The repo commits a shared .claude/settings.json with a permissions allow-list for the project's Docker, dotnet, and gh tooling 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 via gh pr create and reads PR state via gh api. Authenticate once per machine with gh auth login.
  • ctx7npx ctx7@latest fetches 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.

Run locally

.NET Aspire boots PostgreSQL, RabbitMQ, and the services together:

dotnet run --project src/CritterMart.AppHost --launch-profile http

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


Repository Structure

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

Documentation

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

Spec-Driven Development Pipeline

CritterMart's second deliberate purpose is exercising a disciplined SDD pipeline as a teaching artifact in its own right. Every vertical slice flows through:

  1. Context Mapping (cross-BC DDD vocabulary) — docs/context-map/README.md, amended as new BCs appear
  2. Event Modeling workshop (one rolled-up artifact for round one) — docs/workshops/001-crittermart-event-model.md
  3. OpenSpec proposal + Narrative siblings (per slice, must agree) — openspec/changes/{change}/proposal.md + docs/narratives/NNN-{actor}-{journey}.md
  4. Prompt (per session, frozen at session start) — docs/prompts/README.md
  5. 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.


Round-One Scope

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.


Companion Library: JasperFx ai-skills

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.


Contributing

CritterMart's session-driven workflow is documented in docs/prompts/README.md and docs/retrospectives/README.md. Before contributing:

  • Read CLAUDE.md for 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-readmes paired with tidy: docs — add folder READMEs for routing-layer narrowing).
  • All session intent and outcomes are version-controlled in docs/prompts/ and docs/retrospectives/.

Resources


Maintainer

Erik "Faelor" Shafer

LinkedIn · Blog · YouTube · Bluesky

About

A teaching reference architecture for event sourcing in .NET, modeled as a single-seller storefront (ecommerce) on the Critter Stack with Marten and PostgreSQL.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Contributors

Languages