10 · Requirements, NFRs, good enough
When you finish this lesson you should be able to:
- Open a design hour with questions, then assumed answers, not a premature diagram
- List non-functional requirements that change the shape (latency, offline, fan-out, privacy)
- Run a full pass: requirements → NFRs → 6–10 boxes → one deep dive
Core idea
Section titled “Core idea”The requirements pit is jumping to a solution while the job is still mushy. Ask, then lock a version of the truth so you can move: “I’ll assume we must work on a 2G phone, inbox is source of truth, push is best-effort. Correct me.”
Non-functional requirements are the ones that flip the architecture:
- Latency (inline vs async)
- Volume and fan-out
- Offline / flaky network
- Idempotency and money-adjacent “do not double send”
- Privacy / what may leave the device
- Operability (kill switch, audit)
You cannot optimize all of them. Good enough is a named compromise. Decoupling and shared state show up here: if two writers share a row, you will discuss locks, occupancy, or “single writer per aggregate.” If time is coupled (“cron must run after the import”), you will discuss events instead.
The hour’s shape:
- Requirements as verbs (“snooze,” “unsubscribe,” “retry”).
- NFRs in a short list, with one number or a qualitative bar.
- Boxes (lesson 04).
- One deep dive: the riskiest NFR or the consistency rule (lesson 08).
- Leave time to talk failure modes (lesson 05).
Do not deep-dive all boxes. Breadth first, then one hole.
Example
Section titled “Example”“Design notifications for a consumer app.”
Questions (and assumed answers):
- Channels? Push, email, in-app. User can disable each.
- Ordering? In-app is ordered; push is not.
- Offline? Phone may be off for a day; in-app must catch up.
- Scale? Millions of users, tens of notifications per user per day, rare bursts (incident).
- Identity? One user, several devices.
NFRs that matter: at-least-once to the device, idempotent display in the inbox, opt-out honored within minutes, burst must not melt the provider.
Boxes: Client surfaces, Inbox API, Preference service, Fan-out worker, Provider adapters, Template renderer, Audit log.
Deep dive: fan-out on a burst. Not “draw Kubernetes.” Talk partitions by userId, backpressure, and a provider budget. Mention that preferences are read by the worker so a disabled channel does not get a million sends.
flowchart TB surfaces[App email push] inbox[Inbox API] prefs[Preferences] worker[Fan-out worker] providers[Provider adapters] surfaces --> inbox inbox --> worker prefs --> worker worker --> providers worker --> inbox
The last arrow is catch-up: the worker records what the inbox already knows, so a duplicate push does not duplicate the thread.
Drill (15 minutes)
Section titled “Drill (15 minutes)”Full pass, timer on, paper only.
- Prompt: “Design first-session onboarding for a mobile app.”
- Two minutes: questions + assumed answers.
- One minute: NFRs (name four).
- Six minutes: 6–10 boxes.
- Five minutes: deep dive on one of: offline midway, duplicate accounts, or experiment holdout on the first screen.
- Fifteen seconds: what you would watch after launch.
If you still have an empty board at minute eight, you asked too long. Lock assumptions and draw.
Further reading
Section titled “Further reading”- The Pragmatic Programmer — Topic 45, The Requirements Pit
- The Pragmatic Programmer — Topic 5, Good-Enough Software
- The Pragmatic Programmer — Topics 28 and 34, Decoupling and Shared State Is Incorrect State
- A Philosophy of Software Design — Chapter 11, Design it Twice (use it in the deep dive)