A 9-phase TDD evolution where design patterns arrive only when Forces require them — not when I feel like applying them.
- 9 phases of guided evolution from
if/elseto Emergent Design verification — every pattern in this repo has a documented Force that pulled it in - "Patterns arrive, you don't apply them" — I built this to test that claim by feeling the pain first, then extracting the pattern
- AI-assisted hands-on TDD — Claude was the tutor; I typed every line, questioned every concept, and documented every Q&A in
learnings/ - 70+ tests, 25 commits, 6 domain modules built across 3 days, one Phase at a time
- Phase 9 = methodology validation — 3 new features added by modifying nothing in core logic, only adding entries to existing extension points
I had read about Strategy, Factory, Decorator, Chain of Responsibility — but I never experienced them arriving naturally. I knew the names. I didn't know the moment when the codebase says "you need me now."
David Scott Bernstein keeps saying in The Passionate Programmer (12-part Pattern-Oriented Design series): "Patterns arrive, they don't apply." I wanted to test that claim by building a project where every pattern has to justify itself with a concrete pain point before being introduced. No premature abstraction. No "let's add Strategy because it's elegant."
E-commerce checkout was the perfect domain — small enough to fit in a project, deep enough to host every pattern naturally as requirements grow.
The result: a methodology validation, not a product. The codebase is a byproduct. The real output is the lived experience of patterns arriving and the documented Q&A trail in learnings/.
1. Read Phase guide (docs/phases/phaseNN-*.md) — describes the requirement
↓
2. Forces Analysis: what varies, why, how often, independently?
↓
3. RED — write the failing test (Claude provides snippet, I type it)
↓
4. GREEN — minimum code to pass (often just hardcoded if/else)
↓
5. Feel the pain — when if/elif grows, duplication appears, or coupling smells
↓
6. Pattern Signal Detection — does any of the 8 signals fire?
↓
7. REFACTOR — extract the pattern that resolves the Force
↓
8. Q&A and retrospective in learnings/phaseNN-{qa,topic}.md
The code is mine; Claude is the tutor. Every snippet was typed by hand. Every concept I didn't fully grasp became a question. Every question became a learning entry.
| Phase | Theme | Pattern Arrived | Force That Pulled It In |
|---|---|---|---|
| 1 | Simple start | None (if/else) |
Single variation — pattern would be over-engineering |
| 2 | Strategy arrives | Strategy + Factory + Null Object | 4 independent discount rules → if/elif pain → extract |
| 3 | Seam & DI | Protocol + Fake + Constructor injection | External payment APIs are untestable without isolation |
| 4 | Pipeline | Pipeline + Composition | Variable order processing steps, conditional skipping |
| 5 | Abstract Factory | Abstract Factory + Money VO extraction |
Country bundles must be consistent (no KRW + USPS) |
| 6 | Chain of Responsibility | CoR (Fail-fast / Collect-all) | Validators need runtime decision ownership |
| 7 | Decomposition | (structural refactor) | Test files revealing cohesion smells |
| 8 | Variation as Data | Options as list, not subclasses | 5 options would mean 2⁵ = 32 subclasses |
| 9 | Emergent Design verification | (no new patterns — validate) | Add 3 new features to prove OCP holds |
Each phase has a guide in docs/phases/ and a retrospective in learnings/.
The full mapping with rationale and code examples lives in the companion vault note Forces-Driven Pattern Emergence (in my Obsidian KB). The TL;DR:
Every pattern in this codebase exists because a specific Force grew strong enough to require it. You can trace every abstraction back to a moment of pain.
The real test of the methodology. After 8 phases of incremental evolution, can the codebase absorb 3 new features by adding only, not modifying?
Experiment: simultaneously add:
- New country: Germany (EUR + DHL + 19% USt)
- New discount: Student (15%)
- New payment method: Apple Pay (3.0% fee)
Result:
| Target | Added | Modified |
|---|---|---|
discount.py |
StudentDiscount class + 1 dict entry |
0 |
region.py |
GermanyFactory class + 1 char ("EUR") |
1 char |
checkout_router.py |
1 dict entry | 0 |
Files NOT touched: cart.py, checkout.py, pipeline.py, validation.py, cart_router.py, main.py — every core logic file.
All 70 existing tests + new tests passed.
The most striking finding: the Apple Pay domain test passed without writing any implementation. The Phase 3 Seam was so well-abstracted that Apple Pay wasn't a new domain concept — it was just FakePaymentClient(fee_rate=0.030), a new instance of the existing abstraction.
Well-designed abstractions absorb new requirements without domain changes.
| Area | Technology | Notes |
|---|---|---|
| Language | Python 3.12+ | Type hints + Pydantic |
| Web | FastAPI 0.115 | DI via Depends(), Pydantic boundary |
| Server | Uvicorn | Async ASGI |
| DB (planned) | PostgreSQL 16 + SQLAlchemy 2.0 (async) | Phase 1–9 used in-memory; DB integration is post-completion |
| Tests | pytest + pytest-asyncio + httpx | 70+ tests across unit + API |
| Package mgmt | uv | Fast resolver, src layout |
| Container | Docker Compose | Just PostgreSQL |
checkout-engine/
├── src/checkout_engine/
│ ├── domain/
│ │ ├── cart.py # Phase 1 — Item (VO), Cart (Entity), Customer
│ │ ├── discount.py # Phase 2 — DiscountPolicy + 5 Strategies + Factory
│ │ ├── checkout.py # Phase 3 — PaymentClient Seam + CheckoutService + Fake
│ │ ├── pipeline.py # Phase 4, 8 — CheckoutStep Protocol + Pipeline + 8 Steps
│ │ ├── region.py # Phase 5, 9 — Money VO + 4 RegionFactories
│ │ └── validation.py # Phase 6 — Validator Protocol + 4 Validators + 2 Chain modes
│ ├── api/v1/
│ │ ├── cart_router.py
│ │ └── checkout_router.py
│ └── main.py
│
├── tests/unit/ # 70+ tests, one file per domain module
│
├── docs/phases/ # phase01 ~ phase09 — guides for each step
│
├── learnings/ # Per-phase Q&A + retrospective
│ ├── phase01-qa.md
│ ├── phase01-simplicity.md
│ ├── phase02-qa.md
│ ├── phase02-strategy-arrival.md
│ ├── ...
│ └── phase09-final-retrospective.md
│
├── CLAUDE.md # Project instructions for Claude (kept in repo on purpose)
├── tdd-config.yaml # TDD workflow config (phases, test commands)
└── docker-compose.yml # PostgreSQL only
Requirements: Python 3.12+, uv, Docker (optional, for PostgreSQL)
# Install dependencies
uv sync --all-extras
# Run tests
uv run pytest tests/ -v
# Run the API server
uv run uvicorn src.checkout_engine.main:app --reload
# → http://localhost:8000/docs
# Optional: PostgreSQL via Docker
docker compose up -dThe most useful path through this repo isn't the code — it's the journey:
- Read
CLAUDE.md— project philosophy and the "patterns arrive" rule - Read
docs/phases/phase01-simple-start.md— see how each phase guide is structured - Read
learnings/phase01-qa.md— the actual Q&A from working through it - Read
learnings/phase01-simplicity.md— the retrospective - Repeat for phases 2 → 9
- Finally, read
learnings/phase09-final-retrospective.md— the meta-reflection
The git log itself tells the story too — every commit is a deliberate Tidy First step (feat: for behavioral, refactor: for structural, docs: for retrospectives).
Everything in this project is grounded in one or more of these sources:
- The Passionate Programmer — David Scott Bernstein, 12-part Pattern-Oriented Design series. The "patterns arrive" thesis comes from here.
- Object Design Style Guide — Matthias Noback. 46 rules for object design (services, entities, value objects, methods, architecture, testing).
- Test Driven Development: By Example — Kent Beck. The Red-Green-Refactor cycle and test-as-spec.
- Tidy First? — Kent Beck. Separating structural from behavioral changes in commits.
This project is licensed under the MIT License.