Skip to content
academia.sh

Lesson 03 / 13

Functional and Non-Functional Requirements

Showing that the two kinds of requirements are separate determinants: the functional list determining which kind of component is required, thresholds determining how many parts the same work splits into, the change a single threshold produces in the component count when played one at a time, and a threshold's ability to shrink a design.

Contents

The previous lesson separated questions into four families and marked that two families ask different things: actor and flow questions ask what the system will do, threshold questions ask within what limit it will do it. If two sets of answers can change independently of each other, they are two separate determinants.

This lesson shows that independence. The functional requirement list states which kind of component must exist; thresholds state how many parts the same work splits into. When the two are played separately, different things change in the design, and that difference is countable.

Two Determinants

A functional requirement states what the system does and is answered as true or false: the state is returned given a tracking number, the same event arriving a second time is swallowed. The Quality Attributes lesson of the Quality and Testing Fundamentals course had established the same concept under the name “functional expectation”; here the catalog term, functional requirement, is used, and the meaning intended is the same.

A non-functional requirement states within what limit the system does this work, and it is answered by degree. The quality attribute and quality scenario concepts established in that same lesson are not redefined here; a non-functional requirement is the written form of a quality scenario that places a threshold on a quality attribute.

The test of the distinction is this: if a requirement can be checked with a single input–output pair, it is functional. If checking it requires a load, an environment, and a threshold, it is non-functional.

A Threshold Has No Class, but It Has a Source

The first lesson separated numbers into three classes: assumption, computed value, measurement. A threshold is none of these three, and this is deliberate. An assumption is a guess about the world, a computed value comes out of assumptions, a measurement comes out of an apparatus; all three report a fact. A threshold, by contrast, reports not a fact but an acceptance: which value is counted as sufficient.

This is why what is written next to a threshold is not a class but a source: which expectation the threshold comes from. The source of the threshold “the tracking response does not exceed 200 milliseconds” is the response the recipient expects from the tracking page; the source of “event loss is zero” is the delivery obligation in the carrier contract. A threshold with no source written down cannot be argued about, because it is not known on whose behalf it was set.

A non-functional requirement is written in exactly this form — four parts from the quality scenario, and the fifth line is the source field this course adds:

  • Stimulus: the recipient asks about the shipment’s status with a tracking number.
  • Environment: the system is operating at peak load.
  • Response: the shipment’s final state, its zone, and its update time are returned.
  • Response measure: the median response time does not exceed 200 milliseconds.
  • Threshold source: the response the recipient expects from the tracking page.

The second part is empty for now. “At peak load” is not a number, and this scenario is currently unmeasurable; turning the environment into a number is the next lesson’s job.

The Same Function, Two Threshold Sets

The way to show independence is to hold one side fixed and play the other. Six functional requirements are held fixed, and two different threshold sets are tried.

The move from threshold to component is made with a set of rules. These rules are a model, not a universal derivation: each rule ties a threshold condition to a component change, and its rationale stands beside it. The fourth of the rules works in reverse of the other three and removes a component.

// design/requirement.mjs — functional list, threshold sets, and the model producing a component list from both
export const FUNCTIONAL = [
  { no: "F1", flow: "read", action: "return the shipment's state given trackingNo" },
  { no: "F2", flow: "read", action: "return the shipment's route history" },
  { no: "F3", flow: "write", action: "record the state event coming from the carrier" },
  { no: "F4", flow: "write", action: "swallow the same event arriving a second time" },
  { no: "F5", flow: "batch", action: "scan the seller's day and produce a fee" },
  { no: "F6", flow: "batch", action: "deliver the fee report as a file" },
];

export const THRESHOLD = {
  loose: { readMs: 900, eventLoss: 0.01, batchHours: 12, feeMs: 40 },
  tight: { readMs: 200, eventLoss: 0, batchHours: 4, feeMs: 1 },
};

export const SOURCE = {
  readMs: "the response the recipient expects from the tracking page",
  eventLoss: "the delivery obligation in the carrier contract",
  batchHours: "the seller wanting the report in the morning",
  feeMs: "the time the scan can spend per shipment",
};

// The functional list determines which kind of component must exist.
export function baseline(functional) {
  const b = new Set(["service", "shipment-store"]);
  if (functional.some((i) => i.action.includes("fee"))) b.add("tariff-service");
  return b;
}

// Thresholds determine how many parts the same work splits into.
export const RULE = [
  { name: "read threshold under 300 ms", condition: (e) => e.readMs <= 300,
    add: ["tracking-endpoint", "event-receiver"], remove: ["service"] },
  { name: "event loss is not tolerated", condition: (e) => e.eventLoss === 0,
    add: ["event-queue"], remove: [] },
  { name: "batch window under 6 hours", condition: (e) => e.batchHours < 6,
    add: ["batch-worker"], remove: [] },
  { name: "fee call under 1 ms", condition: (e) => e.feeMs <= 1,
    add: [], remove: ["tariff-service"] },
];

export function design(functional, threshold) {
  const component = baseline(functional);
  const triggered = [];
  for (const r of RULE) {
    if (r.condition(threshold) === false) continue;
    for (const b of r.remove) component.delete(b);
    for (const b of r.add) component.add(b);
    triggered.push(r.name);
  }
  return { component: [...component].sort(), triggered };
}
// design/derive.mjs — the same functional list gives two different designs with two threshold sets
import { FUNCTIONAL, RULE, SOURCE, THRESHOLD, design } from "./requirement.mjs";

const count = (a) => FUNCTIONAL.filter((i) => i.flow === a).length;
console.log(`functional requirement = ${FUNCTIONAL.length} (read ${count("read")}, write ${count("write")}, batch ${count("batch")})`);
console.log(`${"threshold".padEnd(11)}${"loose".padStart(7)}${"tight".padStart(6)}  threshold source`);
for (const a of Object.keys(SOURCE)) {
  console.log(`${a.padEnd(11)}${String(THRESHOLD.loose[a]).padStart(7)}${String(THRESHOLD.tight[a]).padStart(6)}  ${SOURCE[a]}`);
}

for (const [name, threshold] of Object.entries(THRESHOLD)) {
  const t = design(FUNCTIONAL, threshold);
  console.log(`\n${name}: rules triggered ${t.triggered.length}/${RULE.length}, components ${t.component.length}`);
  console.log(`  ${t.component.join(", ")}`);
}
functional requirement = 6 (read 2, write 2, batch 2)
threshold    loose tight  threshold source
readMs         900   200  the response the recipient expects from the tracking page
eventLoss     0.01     0  the delivery obligation in the carrier contract
batchHours      12     4  the seller wanting the report in the morning
feeMs           40     1  the time the scan can spend per shipment

loose: rules triggered 0/4, components 3
  service, shipment-store, tariff-service

tight: rules triggered 4/4, components 5
  batch-worker, event-queue, event-receiver, shipment-store, tracking-endpoint

The six functional requirements are the same in both runs. With loose thresholds, no rule triggers and the design stays at three components; with tight thresholds, four rules trigger and the design rises to five components. Whether a design is “three components” or “five components” therefore cannot be read from the functional list.

The composition of the five components is also worth noting: service and tariff-service are not in the list, and tracking-endpoint, event-receiver, event-queue, and batch-worker stand in their place. Because the fourth rule removes a component, tightening does not raise the component count in a single direction. The event queue appearing as a component does not mean the asynchronous delivery mechanism is explained here; the message queue was established in the Caching, Queues and Asynchronous Processing course, and here it is only counted as a component.

Two Directions of Independence

Independence must run in both directions: when an addition is made to the functional list, the thresholds must stay in place; when a threshold is played, the functional list must stay in place. The second script tests both.

// design/independence.mjs — playing the functional list and each threshold one at a time and measuring the change in component count
import { FUNCTIONAL, THRESHOLD, design } from "./requirement.mjs";

const base = design(FUNCTIONAL, THRESHOLD.loose);
console.log(`start: functional ${FUNCTIONAL.length}, threshold loose, components ${base.component.length}`);

const F7 = { no: "F7", flow: "read", action: "return the shipment's estimated delivery day" };
const wider = design([...FUNCTIONAL, F7], THRESHOLD.loose);
console.log(`functional added (${F7.no}): functional ${FUNCTIONAL.length + 1}, components ${wider.component.length}, ` +
  `change ${wider.component.length - base.component.length}`);

console.log(`\n${"single threshold change".padEnd(28)}${"components".padStart(11)}${"change".padStart(8)}  removed/added`);
for (const a of Object.keys(THRESHOLD.loose)) {
  const t = design(FUNCTIONAL, { ...THRESHOLD.loose, [a]: THRESHOLD.tight[a] });
  const removed = base.component.filter((b) => t.component.includes(b) === false);
  const added = t.component.filter((b) => base.component.includes(b) === false);
  const note = [removed.length ? `-${removed.join(" -")}` : "", added.length ? `+${added.join(" +")}` : ""].filter(Boolean).join(" ");
  console.log(`${`${a} ${THRESHOLD.loose[a]} -> ${THRESHOLD.tight[a]}`.padEnd(28)}${String(t.component.length).padStart(11)}` +
    `${String(t.component.length - base.component.length).padStart(8)}  ${note}`);
}
start: functional 6, threshold loose, components 3
functional added (F7): functional 7, components 3, change 0

single threshold change      components  change  removed/added
readMs 900 -> 200                     4       1  -service +event-receiver +tracking-endpoint
eventLoss 0.01 -> 0                   4       1  +event-queue
batchHours 12 -> 4                    4       1  +batch-worker
feeMs 40 -> 1                         2      -1  -tariff-service

The first two lines give one direction: when the seventh functional requirement is added, the component count stays at 3, a change of 0. The new function falls inside the existing read flow and does not move the component list. The intuition that a design grows as the functional list grows is falsified by this line.

The table gives the other direction. When each of the four thresholds is played on its own, the functional list stays at six items, yet the component count changes: three thresholds raise it from 3 to 4, one lowers it from 3 to 2. The last row is the most instructive. When the fee call’s budget is pulled from 40 milliseconds to 1 millisecond, the tariff service cannot stay a separate component, because being a separate component costs a network hop, and that hop does not fit the budget. A tight threshold does not always ask for more parts; sometimes it asks for the opposite.

The removed-versus-added distinction in the first row also carries information. When the read threshold tightens, service is removed and two components take its place, meaning this is not an addition but a split. The other two thresholds only add. Whether a design change is a split or an addition determines its effect on the contract surface: a split divides an existing boundary in two, an addition brings in a new boundary.

Summary

  • A functional requirement states what the system does and is checked with a single input–output pair; a non-functional requirement states within what limit it does it and requires a load, an environment, and a threshold.
  • A threshold has no assumption, computed value, or measurement class; a threshold is an acceptance, and what is written next to it is not a class but a source — which expectation it comes from.
  • A non-functional requirement is written with the quality scenario’s four parts plus the threshold’s source; the example scenario’s environment part is still numberless.
  • The same six functional requirements gave 3 components with loose thresholds and 5 with tight thresholds; the component count cannot be read from the functional list.
  • The seventh functional requirement did not change the component count (a change of 0); each of the four thresholds changed it on its own (three by +1, one by −1).
  • A tight threshold does not always ask for more components: when the fee call’s budget was pulled to 1 millisecond, the tariff service stopped being a separate component.

Next Step

Three of the quality scenario’s four parts were written, one stayed empty: the environment. The sentence “operating at peak load” leaves the threshold unmeasurable, because how many requests peak load is has not been written down. The same gap exists in the thresholds themselves — whether 200 milliseconds is achievable cannot be said without knowing how many requests arrive per second. The next lesson fills this gap: the six unanswered questions that turned into six assumptions in the previous lesson are gathered into an assumption table, and request rate, data growth, and bandwidth need are derived from the table by arithmetic.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close