Skip to content

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

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:

  1. Requirements as verbs (“snooze,” “unsubscribe,” “retry”).
  2. NFRs in a short list, with one number or a qualitative bar.
  3. Boxes (lesson 04).
  4. One deep dive: the riskiest NFR or the consistency rule (lesson 08).
  5. Leave time to talk failure modes (lesson 05).

Do not deep-dive all boxes. Breadth first, then one hole.

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

Full pass, timer on, paper only.

  1. Prompt: “Design first-session onboarding for a mobile app.”
  2. Two minutes: questions + assumed answers.
  3. One minute: NFRs (name four).
  4. Six minutes: 6–10 boxes.
  5. Five minutes: deep dive on one of: offline midway, duplicate accounts, or experiment holdout on the first screen.
  6. 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.

  • 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)