The Red-Green-Refactor Loop

▶ Watch (03:14)

Simon builds a university registration system in Java using what he calls the tightrope metaphor: write a test, balance one way; make it pass, balance back. The first test sends a POST to /students and asserts a 201 status. The implementation is a controller that returns that status code and nothing more. Each cycle adds one assertion (allocate an ID, confirm the ID is not null, add a name field) then one small implementation. The refactor step follows immediately every time, because that step is what keeps the design clean as requirements grow.

Naming Code in the Language of the Domain

▶ Watch (07:20)

The controller method is named registerNewStudent, not post or create. A student was probably born 19 years ago, Simon notes — not when they access the system. A static factory method called register on the Student class carries that same vocabulary into the domain model. Simon also shows Contextive, an IDE extension and browser plugin he built that reads a YAML glossary and surfaces term definitions on hover, inside both the code editor and live web pages. The goal is a shared language that holds across the codebase, the docs, and the stakeholder conversation.

Growing the Model Incrementally

▶ Watch (16:10)

Once students work, Simon runs through courses and rooms at speed, each following the same pattern: a failing test, a hard-coded return, then parameterization that forces real persistence. Adding a JPA repository feels like a large step, but because the surrounding structure was already there, it is a small commit. One noteworthy design choice: the test class deserializes API responses into a separate, test-side Student class rather than the application entity. If a refactor accidentally changes the entity’s shape, the test catches the contract break. Using the application class would let that regression pass silently.

Domain Services and the Refactor Discipline

▶ Watch (23:44)

The enrollment capacity check in the capacity check implementation (30:24) starts as inline controller code: load the room, count enrollments by course ID, check if that count plus one exceeds capacity. It passes. Then Simon refactors. The room gets a method named wouldEnrollmentExceedCapacity. That logic moves into a domain service called Enroller, which handles database calls so the controller has almost nothing left to do. Folders are organized by domain concept, with controller and domain model sitting side by side. Simon argues there is a commercial imperative: keeping the codebase clean keeps the team efficient.

Event Storming Catches What Tests Cannot

▶ Watch (36:11)

All tests are green, then stakeholders report 21 enrollments in a room with 20 seats. The code checks capacity correctly, but concurrency breaks it. Two requests read 19 enrollments simultaneously, both pass the check, both commit. The repeat-until-failure concurrency test (39:13) confirms it: IntelliJ’s repeat-until-fail hits the race condition within three loops. Before reaching for a database lock, a team member calls for an event storm. That conversation exposes the wrong model: rooms should not be pre-assigned to courses. The right design collects enrollments first, then maps courses to rooms by actual demand once a deadline passes.

Notable Quotes

next time I’m going to tidy it up when Christopher Simon (quoting his daughter) · ▶ 32:06

I think there’s a commercial imperative to Christopher Simon · ▶ 33:07

probably born 19 years ago. So create Christopher Simon · ▶ 24:24

Key Takeaways

  • Write the smallest test that fails, make it pass with the simplest code, then refactor before moving on.
  • Use domain vocabulary in method names so code reads like the business rules it enforces.
  • Event storming with stakeholders reveals process assumptions that green tests will never catch.