Skip to content

tomato-data/checkout-engine

Repository files navigation

Checkout Engine

Korean

A 9-phase TDD evolution where design patterns arrive only when Forces require them — not when I feel like applying them.

Highlights

  • 9 phases of guided evolution from if/else to 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

Why I Built This

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


How I Studied

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.


The 9 Phases

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

Forces → Pattern map

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.


Phase 9: OCP Validation Result

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:

  1. New country: Germany (EUR + DHL + 19% USt)
  2. New discount: Student (15%)
  3. 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.pyevery 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.


Tech Stack

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

Project Structure

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

Setup

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 -d

Reading Order (if you're here to learn)

The most useful path through this repo isn't the code — it's the journey:

  1. Read CLAUDE.md — project philosophy and the "patterns arrive" rule
  2. Read docs/phases/phase01-simple-start.md — see how each phase guide is structured
  3. Read learnings/phase01-qa.md — the actual Q&A from working through it
  4. Read learnings/phase01-simplicity.md — the retrospective
  5. Repeat for phases 2 → 9
  6. 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).


References

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.

License

This project is licensed under the MIT License.

About

A 9-phase TDD evolution where design patterns arrive only when Forces require them. AI-assisted hands-on learning of Strategy, Factory, DI, Pipeline, Abstract Factory, CoR, and more — built one Force at a time.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages