07 · Walk a model
When you finish this lesson you should be able to:
- Run Evans’s chapter 7 sequence without using his cargo domain
- Narrate a user action as a chain of root methods
- Change one unplanned feature by extending the language, and notice what breaks
- Treat the first aggregate cut as a guess you expect to refactor
The chapter 7 habit (not the cargo)
Section titled “The chapter 7 habit (not the cargo)”Evans spends a long chapter on cargo shipping: apps vs domain, entities vs values, associations, aggregate boundaries, repositories, scenarios, factories, a pause to redesign the cargo aggregate, modules, then a new feature (allocation checking).
You do not need cargo. You need the order of operations:
- Isolate the domain (applications are thin).
- Distinguish entities and values.
- Design associations you can implement.
- Draw aggregate boundaries.
- Select repositories (roots only).
- Walk scenarios as method calls.
- Make creation go through factories.
- Pause. If the aggregate is awkward, redesign it before adding more.
- Place modules according to language.
- Add a feature you did not start with. The language should stretch, not snap.
A walk that never adds an unplanned feature is a canned demo. The allocation-checking moment in the book is the point: a new word in the expert’s mouth should already have a home, or you learn the model is shallow.
Slice: message + optional push
Section titled “Slice: message + optional push”Applications: PostToThread, MarkRead, RequestPush. No business ifs.
Entities: InboxThread, Account (id from identity context).
Values: MessageBody, QuietHours, Channel.
Associations: Thread → participant ids (one way). Not User → all threads.
Aggregates: InboxThread (messages, cursors). PushDelivery separate.
Repositories: ThreadRepository, DeliveryRepository.
Scenario — reply
threads.get(id)thread.append(authorId, body)— asserts participant, not archivedthreads.add(thread)- If notification policy allows, application records
DeliveryRequested
Pause for redesign. If append is also computing mute windows, quiet hours, and experiment holdout, the thread aggregate swallowed two other domains. Pull NotificationPolicy (value or small aggregate per user) back out. That pause is the chapter.
Unplanned feature — snooze 24h.
Wrong: snoozedUntil on every message.
Better: MuteWindow on the participant inside the thread, or policy aggregate per user. Walk both. Keep the invariant you can say in one sentence.
sequenceDiagram participant App participant Repo participant Thread App->>Repo: get threadId Repo-->>App: InboxThread App->>Thread: append author body App->>Repo: add thread App->>App: request push if policy allows
If append knows SMTP, isolation failed. Go back to lesson 04.
What you should feel in your hands
Section titled “What you should feel in your hands”- Loading one thread does not load the company.
- You cannot
getMessagefrom a global store without the thread id (local identity). - A new engineer can walk the scenario from the type names.
If any of those fail, the walk found a bug in the model, which is the successful outcome.
Drill (25 minutes)
Section titled “Drill (25 minutes)”Holdout on first session.
- Ten minutes: entities, values, two aggregates, two repositories, one factory, one module map.
- Five minutes: walk “user opens first session and must stick to variation.”
- Five minutes: add “kill switch off for everyone within minutes.”
- Five minutes: write what broke. Park that sentence for lessons 08–12. Do not patch it with a boolean named
kill_switch_hack.
Further reading
Section titled “Further reading”- Domain-Driven Design — Chapter 7, Using the Language: An Extended Example (the full walk; pause and redesign; new feature as test of the language)