Skip to content
academia.sh

Lesson 05 / 13

Communicating the Design

Presenting a design in a defensible form: a box-and-arrow diagram whose edge labels are generated from the calculation, a five-field decision record made of scenario, threshold, value, the value's class, and the cost paid, counting missing fields, and screening unmeasured sentences out of the narrative.

Contents

The previous four lessons produced a design’s material: five components and seven contracts, a narrowed scope, four thresholds, and twenty-one computed values coming out of thirteen assumptions. This material is not a design on its own. A design exists to the extent that it can be defended once it has been explained to someone else and that person has pushed back.

This lesson takes up the form of that explanation. The form consists of three parts: a diagram showing the flows and boundaries, a decision record that logs each decision with five fields, and the class standing next to each number. The style selection and decision table established in the Architectural Styles course are not re-explained here; that course’s three-part defense form is used together with this course’s number classes.

What a Diagram Must Show

A design diagram is not decoration; it is a document that lets a reader agree with a decision or push back on it. To do this, it must carry four things: components, the flow’s direction, which flow an edge belongs to, and the number on the edge. The fourth is usually missing, and when it is missing the diagram is just an arrangement of boxes.

Writing edge numbers by hand carries two risks: the diagram is not updated when an assumption changes, and which class a number belongs to gets lost. The fix for both is to generate the diagram from the calculation. The first file below recalculates, from the previous lesson’s assumption table, only the numbers that appear in this narrative.

// design/calculation.mjs — the numbers used in the narrative; inputs come from the Back-of-the-Envelope Estimation lesson's assumption table
const DAY_SECONDS = 86_400;
export const V = { dailyUsers: 2_000_000, queriesPerUser: 6, dailyShipments: 400_000,
  eventsPerShipment: 7, peakFactor: 3, cacheHitRate: 0.9, batchSpanDays: 30,
  batchWindowHours: 4, shipmentRecordBytes: 900, eventRecordBytes: 220, retentionDays: 730 };

const dailyBytes = V.dailyShipments * V.shipmentRecordBytes + V.dailyShipments * V.eventsPerShipment * V.eventRecordBytes;
export const C = {
  readPeak: ((V.dailyUsers * V.queriesPerUser) / DAY_SECONDS) * V.peakFactor,
  writePeak: ((V.dailyShipments * V.eventsPerShipment) / DAY_SECONDS) * V.peakFactor,
  storeReads: ((V.dailyUsers * V.queriesPerUser) / DAY_SECONDS) * V.peakFactor * (1 - V.cacheHitRate),
  scan: (V.batchSpanDays * V.dailyShipments) / (V.batchWindowHours * 3600),
  msPerShipment: (V.batchWindowHours * 3600 * 1000) / (V.batchSpanDays * V.dailyShipments),
  storedGB: (dailyBytes * V.retentionDays) / 1e9,
};

The drawing consists of a box-and-arrow layout, and the edge labels come from the values above. The class letters stand at the end of the label: A assumption, C computed value, M measurement.

// design/diagram.mjs — box-and-arrow drawing whose edge labels come from the calculation (A assumption, C computed value, M measurement)
import { C } from "./calculation.mjs";

const e = (x, unit, cls) => `${x.toFixed(2)} ${unit} ${cls}`;
const box = (label, inner) => `|${(` ${label}`).padEnd(inner)}|`;
const B = { EDGE: "+-------------------+", STORE: "+------------------+" };
function line(...part) {
  let s = "";
  for (const [col, text] of part) s = s.padEnd(col) + text;
  return s;
}

const drawing = [
  line([2, "outside actor"], [30, "edge component"], [59, "shared component"]),
  "",
  line([2, "recipient, seller"], [30, B.EDGE]),
  line([3, "tracking query ----> "], [30, box("tracking-endpoint", 19)], [51, " ---+"], [59, e(C.storeReads, "requests/s", "C")]),
  line([3, e(C.readPeak, "requests/s", "C")], [30, B.EDGE], [55, "|"]),
  line([55, "|"]),
  line([2, "carrier"], [30, B.EDGE], [55, "|"], [59, B.STORE]),
  line([3, "state event ----> "], [30, box("event-receiver", 19)], [51, " ---+-> "], [59, box("shipment-store", 18)]),
  line([3, e(C.writePeak, "requests/s", "C")], [30, B.EDGE], [55, "|"], [59, `| ${e(C.storedGB, "GB", "C").padEnd(17)}|`]),
  line([55, "|"], [59, B.STORE]),
  line([2, "seller"], [30, B.EDGE], [55, "|"]),
  line([3, "end-of-day request ----> "], [30, box("batch-worker", 19)], [51, " ---+"], [59, e(C.scan, "records/s", "C")]),
  line([3, "1 job/day A"], [30, B.EDGE]),
];
for (const s of drawing) console.log(s);
console.log(`\nbatch job margin per shipment = ${C.msPerShipment.toFixed(2)} ms C`);
  outside actor               edge component               shared component

  recipient, seller           +-------------------+
   tracking query ---->       | tracking-endpoint | ---+   41.67 requests/s C
   416.67 requests/s C        +-------------------+    |
                                                       |
  carrier                     +-------------------+    |   +------------------+
   state event ---->          | event-receiver    | ---+-> | shipment-store   |
   97.22 requests/s C         +-------------------+    |   | 712.48 GB C      |
                                                       |   +------------------+
  seller                      +-------------------+    |
   end-of-day request ---->   | batch-worker      | ---+   833.33 records/s C
   1 job/day A                +-------------------+

batch job margin per shipment = 1.20 ms C

The first thing the diagram says is that three outside actors enter three separate edge components, and all three converge on the same component. This makes visible the fact the first lesson measured: shipment-store is the component all three flows touch, and every constraint accumulates there first.

The second thing is the asymmetry of the edge numbers. The left edge carries 416.67 requests a second; the same flow’s right edge carries 41.67. That difference is a component decision in its own right, and it shows up in the diagram as a single pair of numbers. A diagram with no number on its edges hides this difference; a reader assumes the three arrows carry an equal load.

Third is the distribution of the class letters. Four of the five edges are C, one is A. The end-of-day request arriving once a day is not a computed result but a chosen assumption, and its letter says so. If an edge carried M, it would read that the number was measured on this machine and would change in another environment.

The Decision Record

The diagram shows the state, not the decision. The answer to “why three separate edges” or “why is the tariff rule not a separate component” stands in a separate record. The record consists of five fields, and three of them come from the defense form in the Architectural Styles course.

  • Scenario: which quality is being asked about, under which stimulus.
  • Threshold: the accepted value for that quality and its source.
  • Value: the number the decision achieves on that measure.
  • Value’s class: assumption, computed value, or measurement.
  • Cost: the number that grows in exchange — the other half of the trade-off.

Without the fifth field, the record is a declaration, not a defense. Four decisions are written below with these five fields; two fields in the fourth are left empty on purpose.

// design/narrative.mjs — the decision record's five fields and the measure check on narrative sentences
import { C } from "./calculation.mjs";

const DECISION = [
  { name: "read and write are placed in separate components",
    scenario: "the recipient makes a tracking query while a write wave is running",
    threshold: "tracking response median at most 200 ms",
    value: `${C.writePeak.toFixed(2)} requests/s of write does not fall on the same process`, class: "computed value",
    cost: "components 3 -> 4, 1 new boundary" },
  { name: "tariff rule is called inside the process",
    scenario: "the end-of-day job scans thirty days of shipments",
    threshold: "at most 1 ms per shipment",
    value: `${C.msPerShipment.toFixed(2)} ms margin`, class: "computed value",
    cost: "the tariff cannot be published separately, 2 parts published together" },
  { name: "tracking response is served from cache",
    scenario: "the same tracking number is asked again within a short interval",
    threshold: "reads reaching the store at most 50 requests/s",
    value: `${C.storeReads.toFixed(2)} requests/s`, class: "computed value (depends on the 0.90 hit rate assumption)",
    cost: "the response can be as stale as one cache age" },
  { name: "end-of-day job runs in a four-hour window",
    scenario: "the seller wants the report in the morning",
    threshold: `scan at least ${C.scan.toFixed(2)} records/s`,
    value: null, class: null,
    cost: "the scan cannot run outside the window" },
];

const FIELD = ["scenario", "threshold", "value", "class", "cost"];
for (const k of DECISION) {
  const missing = FIELD.filter((a) => k[a] === null);
  console.log(`${k.name}\n  ${5 - missing.length}/5 fields${missing.length ? ` — missing: ${missing.join(", ")}` : ""}`);
}
const complete = DECISION.filter((k) => FIELD.every((a) => k[a] !== null));
console.log(`\ndefensible decisions = ${complete.length}/${DECISION.length}`);
console.log(`decisions with cost written = ${DECISION.filter((k) => k.cost !== null).length}/${DECISION.length}`);

const SENTENCE = [
  "the read path is independent of the write wave",
  "peak request rate reaching the store is 138.89/s",
  "the design scales",
  "the end-of-day job scans 10.80 GB",
];
console.log("");
for (const s of SENTENCE) console.log(`${/\d/.test(s) ? "measured  " : "unmeasured"}: ${s}`);
console.log(`unmeasured sentences = ${SENTENCE.filter((s) => /\d/.test(s) === false).length}/${SENTENCE.length}`);
read and write are placed in separate components
  5/5 fields
tariff rule is called inside the process
  5/5 fields
tracking response is served from cache
  5/5 fields
end-of-day job runs in a four-hour window
  3/5 fields — missing: value, class

defensible decisions = 3/4
decisions with cost written = 4/4

unmeasured: the read path is independent of the write wave
measured  : peak request rate reaching the store is 138.89/s
unmeasured: the design scales
measured  : the end-of-day job scans 10.80 GB
unmeasured sentences = 2/4

Three of the four decisions carry all five fields, one carries three. What the missing one lacks also matters: its scenario, threshold, and cost paid are written, but the value achieved against the threshold and that value’s class are empty. This is the most common gap in design texts: a decision is declared together with a threshold, but that the threshold is met is not shown. For the four-hour window decision to be defensible, it must be shown that the scan keeps up with 833.33 records a second, and that number’s class must be stated — a computed value with a single worker, a measurement once the worker count is chosen.

The cost lines are also worth reading. All four decisions write a cost, and none of the costs is an unmeasured word like “complexity”: a rise in the component count, the number of parts published together, the response’s chance of going stale, the scan’s inability to run outside the window. A decision record that writes its cost in unmeasured words is counted as not having written a cost at all.

The Unmeasured Sentence

The last check looks at the narrative itself. Two of the four sentences carry no number: “the read path is independent of the write wave” and “the design scales.” Both appear often in design texts, and neither says anything. The first can be fixed: independence’s measure is how much of the write flow’s growth passes through to the read response, and that can be written as a number. The second cannot be fixed, because it does not say which quality degrades by how much under which load; unless a quality, a load, and a threshold are written in its place, the sentence is discarded.

The two sentences that do carry a number must also carry their class. “Peak request rate reaching the store is 138.89” is a computed value that depends on six assumptions, and it must be presented as one; “the end-of-day job scans 10.80 GB” is the same. For a design narrative to be checkable means that every number inside it can be traced back to its source.

Summary

  • A design narrative consists of three parts: a diagram showing flow and boundary, a five-field decision record, and the class standing next to each number.
  • A diagram must carry component, direction, flow, and edge counts; when edge labels are generated from the calculation, they stay current as assumptions change and their class is never lost.
  • In the example diagram, edge asymmetry makes a component decision visible: the same flow carries 416.67 requests/s on its left edge and 41.67 requests/s on its right edge.
  • The decision record’s five fields are scenario, threshold, value, the value’s class, and the cost paid; in the example, three of four decisions carried all five fields, and one was counted undefensible because its value and class fields stayed empty.
  • A trade-off cannot be written in an unmeasured word; the four decisions’ costs were written as the component count, parts published together, the chance of staleness, and the window constraint.
  • Two of the four sentences in the narrative carried no number; a sentence with no number is either rewritten with a measure or discarded.

Next Step

This topic showed how a design is built and how it is explained: separating components, the two halves of a requirement, the move from assumption to computed value, and tying every decision to a rationale. What the rationale rests on has still not been named. This lesson’s thresholds used measures like “tracking response median,” “requests reaching the store,” and “finishing inside the window” as if they were already known concepts, but the qualities behind sentences like “fast,” “scales,” and “stays up” were not defined one by one. The next topic defines these measures in turn, and its first lesson opens with the pair most often confused with each other: latency and throughput are the answers to two separate questions about the same system, and one improving does not mean the other improves.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close