Advanced System Design Tutorial 0/70 lessons ~6 min read Lesson 2

    Architectural Thinking and Quality Attributes

    A quality adjective cannot be designed for, tested, or defended. "The checkout shall be fast, scalable, and secure" survived three planning meetings at TechLearningPro and still le

    Course progress0%
    Focus
    15 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Architectural Thinking and Quality Attributes

    Advanced System Design & Software Architecture · Lesson 02 · Software Architecture Foundations

    Lesson Overview

    A quality adjective cannot be designed for, tested, or defended. "The checkout shall be fast, scalable, and secure" survived three planning meetings at TechLearningPro and still left the squad free to ship a price quote that was quick on a laptop and unbounded at the ninety-ninth percentile. Lesson 1 required a stimulus and a number. This lesson turns that intent into a quality attribute scenario, then shows how to let the highest-priority scenarios choose structures. The method is architectural thinking: every proposal is a claim about a measurable response, under a stated condition, bought with a tactic that makes something else worse.

    The five lenses for this course are latency, availability, scalability, security, and modifiability. They sit on top of ISO/IEC 25010, they do not replace it. Latency is time behaviour. Availability is a reliability characteristic with a user-visible definition of failure. Scalability is a growth story about capacity, not a license to ignore latency. Security is a set of characteristics (confidentiality, integrity, accountability among them) placed in a structure. Modifiability is the cost of a named class of change. Interoperability, observability, testability, and cost appear as soon as a scenario needs them. They are not a second backlog of slogans.

    You will write scenarios in the six-part form used by Bass, Clements, and Kazman, distinguish tactics from patterns, and run one iteration of Attribute-Driven Design (ADD) on a commerce checkout. The sibling primer /learn/enterprise-architecture/quality-attributes introduces the vocabulary. /learn/enterprise-architecture/availability and /learn/enterprise-architecture/architecture-scalability survey those two qualities. Here the deliverable is a scenario a design review can fail, a tactic tied to that scenario, and a trade-off the stakeholder who pays has seen. Service-level objectives as an operating practice return in /learn/advanced-system-design/non-functional-requirements-and-slo-design. This lesson stops at the architectural claim and a single Micrometer timer on the path the claim names.

    Learning Objectives

    By the end of this lesson you will be able to:

    1. Write a quality attribute scenario with source, stimulus, artifact, environment, response, and response measure.
    2. Reject a measure that has no percentile, no window, or no definition of user-visible failure.
    3. Map latency, availability, scalability, security, and modifiability onto design tactics and name the quality each tactic taxes.
    4. Run one ADD iteration: select drivers, choose an element to refine, apply a tactic, and record what the tactic makes worse.
    5. Explain the difference between a tactic, a pattern, and a reference architecture well enough to stop a review that starts from a diagram.
    6. Put a Micrometer timer on the path a latency scenario names and compare a sample to a budget in plain Java.
    7. Judge a commerce proposal when price-quote latency, capture integrity, and modifiability of tax rules compete.

    Prerequisites

    • Lesson 1: stakeholders, constraints, quality intents, and the three families of structure. Scenarios refine intents; they do not replace the brief.
    • Java 21 and the ability to read a Spring Boot 3 component. The probe in this lesson uses Micrometer, which Spring Boot brings in through Actuator. You will not configure a full observability stack.
    • Comfort with percentiles. If "p95" is new, treat it as "95 percent of observed values are at or below this number" and read the latency section before the code.
    • No queueing-theory background. Capacity math is lesson 24. This lesson asks only whether the measure names a load.

    Detailed Core Concepts

    Architectural thinking

    Architectural thinking is the habit of refusing a structure until it is attached to a response you can observe. Engineers already think in mechanisms: a cache, a lock, a retry, a service split. Mechanisms are answers. Thinking starts from the question the mechanism is allowed to answer.

    The habit has four moves, and they are cheap enough to use in a design review without a workshop.

    1. Name the response. What will a stakeholder experience or an auditor extract? A status code, a row that exists, a change that touches one module, an unauthorized call that is denied.
    2. Name the condition. Under what load, failure, or change pressure? "At peak" is not a condition until peak is a number or a trace of last season's peak, labeled as an assumption when it is one.
    3. Name the element. Which structure is responsible? A whole-system claim ("the platform is 99.9 percent available") hides the path that actually hurts the user.
    4. Name the tax. Which other response gets worse, and who accepted it?

    Teams that skip to a mechanism draw it into the diagram and then go looking for a justification. That search is how message buses, caches, and service splits accumulate without owners. The four moves are also how you read someone else's proposal: if any move is missing, the proposal is not yet architecture.

    The six-part scenario

    A quality attribute scenario is a short claim with six slots. Memorize the slots. Every later lesson in this course will expect them when a quality is in dispute.

    SlotQuestionWeak exampleReviewable example
    SourceWho or what generates the stimulus?"Users""A signed-in learner with a cart of at most 20 lines"
    StimulusThe event that arrives"Uses checkout""Requests a price quote"
    ArtifactThe element that must respond"The system""The price-quote path in the catalog process"
    EnvironmentThe condition of the system"Normal""Steady midday load, payment vendor healthy, price cache warm"
    ResponseWhat the artifact does"Is fast""Returns an itemized total without placing an order"
    Response measureHow we know the response is good enough"Fast""p95 under 200 ms and p99 under 500 ms, measured at the API, over 15 minutes"

    General scenarios describe a family ("a developer changes a tax rule"). Concrete scenarios bind one family to this system ("a developer changes VAT for one country; the diff touches the tax module and its tests; no change in the payments adapter; the change is deployable in one working day"). Design uses concrete scenarios. A utility tree, in lesson 3, is a prioritized set of them.

    A scenario without an environment is a lab result. A scenario without an artifact is a slogan about the company. A response measure that cannot be collected is a wish. "User satisfaction" can be a business goal. It becomes architectural only when you name the observable that stands in for it and the error you accept by using a stand-in.

    Latency

    Latency is the elapsed time of one interaction on one path. Throughput is how many interactions complete per unit time. They move together only inside a narrow range. A design can raise throughput by batching and make a single learner wait longer. A design can cut median latency with a cache and leave the tail dominated by cache misses and lock waits.

    Report a percentile and a window, not a mean. Means hide the tail the learner actually hits during a sale. For the TechLearningPro price quote, the planning scenario uses p95 and p99 because support load correlates with the tail, not with the median. State where the clock starts and stops. "200 ms" at the browser includes the learner's network. "200 ms" at the API does not. Mixing them makes every review a quarrel about a number that was never located.

    Tail latency has causes the average will not show: garbage-collection pauses, lock convoys, a downstream call with a retry, a cold cache, a noisy neighbor on the same pool. The architectural response is first to isolate the path (its own pool, its own timeout, its own budget) and only then to add copies or caches. Isolation is a tactic. It is often cheaper than a new service and easier to reverse.

    Timeouts are part of the latency design. A price quote that waits 30 seconds on a tax service has no p95, regardless of how quick the local code is. Bound the downstream call, and define the response when the bound is hit: fail the quote, or return a total marked stale. Both are architectures. An unbounded wait is an accident.

    Availability

    Availability is the fraction of time a named function gives its defined response. The arithmetic of "nines" is easy and usually premature. Three nines across a year is a little under nine hours of unavailability if you measure the whole year as one window. The architectural content is elsewhere:

    • What is down. A TCP health check can pass while price quotes time out. The artifact in the scenario is the function, not the process.
    • For whom. A single-tenant outage and a platform outage are different stimuli. Enterprise buyers will ask which one you measured.
    • What is lost. A restart that drops in-flight quotes is a different event from a restart that loses a captured payment. Lesson 1's Harborline decision (no durable state in the process) is an availability tactic aimed at committed data, not at in-flight latency.
    • How it returns. Mean time to recovery dominates many user-visible outages. A standby you have never failed over to is not a response measure. It is a hope. Disaster recovery as a program is lesson 64. Here, the scenario must say whether the response includes a human and how long that human has.

    Redundancy is the tactic people reach for first. It costs money, operational skill, and often consistency: two copies disagree, or a failover promotes a replica that is seconds behind and a learner sees a progress row vanish and return. The trade-off belongs in the decision record next to the nine. Degrading the function (quotes unavailable, playback of already-fetched segments continues) is also an availability tactic, and for some stakeholders it is the better one.

    Scalability

    Scalability is the ability to absorb a stated growth in workload by adding a stated kind of resource, while a stated quality (usually latency or error rate) stays inside its measure. It is not a synonym for "fast," and it is not free elasticity. A process that keeps session state in memory can be fast for one user and unable to add a second instance without sticky routing or lost carts. That is a scalability limit created by an allocation choice.

    Two directions matter in review:

    • Scale up. More CPU, memory, or I/O on one node. Simple operationally, bounded by the largest node you are willing to run, and often the right answer at the loads in this module's assumptions.
    • Scale out. More nodes. Requires a story for state, for the database, and for anything that must happen once (a payment capture, a certificate issuance). The database is frequently the element that does not scale out just because the API did.

    A credible scalability scenario names the multiplier ("ten times catalog reads, writes flat"), the resource you will add, the quality that must hold, and the element you expect to saturate first. "We will shard" is a mechanism from lesson 30. It is allowed into the diagram when the scenario's multiplier exceeds what the current store can do and someone owns the shard key. Writing the multiplier too small is as damaging as writing it too large: the design grows mechanisms the load will not pay for.

    Security

    Security scenarios are easy to fake with the word "unauthorized." A reviewable scenario names the source (an authenticated learner, an anonymous caller, a support tool, a compromised dependency), the stimulus (reads another learner's progress, replays a capture request, exfiltrates a token), the artifact (the API, the object store, the log pipeline), the environment (production, token still inside its lifetime), the response (deny, audit, contain), and the measure (denied with a recorded principal and object id, no payload in the log, within a stated detection time when detection is the point).

    Tactics group into resist, detect, react, and recover. Resist is what structure can do: authenticate at the edge, authorize in the component that owns the data, encrypt on the path that leaves the trust boundary, separate the PCI segment from the progress store so a flaw in one does not hand over the other. Lesson 1 placed card data at the vendor for this reason. Detect is logging and alerting that a person can use; lesson 43 designs the pipeline. This lesson's obligation is to refuse a security claim that has no artifact. "We use HTTPS" does not say whether a support token can read every tenant.

    Threat modeling as a facilitated practice is lesson 63. You do not need the full practice to write one scenario that would embarrass the design if it failed.

    Modifiability

    Modifiability is the cost of change, measured in what a named change may touch and how long a competent team needs. Cohesion and coupling are the underlying properties. Lesson 4 works them at class level. Here the element is a module or a service boundary.

    A modifiability scenario sounds like this. Source: a developer on the catalog squad. Stimulus: a new VAT rule for one country. Artifact: the tax module. Environment: the payments vendor adapter is unchanged and in production. Response: the rule is implemented behind the existing tax interface, with tests, and no edit to capture flow. Measure: one module's diff, reviewable in a day, deployable without a vendor certification.

    If the honest measure is "a week and three squads," the architecture does not have the modifiability the roadmap assumes. That is a useful finding. It means either the roadmap slips, the structure changes, or the team accepts the cost in public. What is not useful is calling the code "clean" because it uses interfaces, while every interface change ripples into the vendor adapter.

    Binding time is part of modifiability. A tax rate compiled into a binary and a tax rate read from a table the finance team can edit are different designs. Later binding improves one class of change and creates a new availability and integrity problem: a bad table edit is now a production incident. Name that tax.

    Tactics, patterns, and reference architectures

    A tactic is a single design decision known to influence a quality attribute response. "Introduce a time bound on the downstream call" is a latency tactic. "Keep committed state out of the process heap" is an availability tactic. "Authorize in the data owner" is a security tactic. "Hide the tax rule behind an interface" is a modifiability tactic.

    A pattern packages several tactics and several responsibilities into a recurring structure. A cache is a pattern that applies the "maintain multiple copies" tactic and immediately creates a freshness trade-off. A layered monolith packages modifiability tactics and can damage latency if every call crosses a chatty boundary. Patterns are taught for their mechanics in /learn/design-patterns and in module 2 of this course. In an architecture review the pattern is admissible when the scenario is already written and the pattern's known tax is accepted.

    A reference architecture (microservices, event-driven, clean architecture, a vendor landing zone) is a bundle of patterns aimed at a common set of drivers. Adopting one because it is a reference, before your scenarios exist, imports its taxes without importing its drivers. Clean architecture's dependency rule is lesson 5. Events are lesson 9. Both are legitimate once a scenario says why the bundle's tax is the one you want to pay.

    Attribute-Driven Design in one iteration

    ADD, in the form published by the Software Engineering Institute and described by Cervantes and Kazman, designs by iteration over drivers. You do not finish the system in one pass. One iteration is enough to change how you draw.

    1. Confirm the inputs you actually have: the brief, the concrete scenarios, the constraints, and the design purpose of this iteration ("decide the price-quote and capture paths," not "design commerce").
    2. Choose the iteration goal by selecting a few drivers. If you select all of them, you have selected none.
    3. Choose the element to refine. Early on, the element may be the whole system. Later it is one path.
    4. Choose design concepts: tactics first, then a pattern if it carries those tactics, then a technology only if a constraint or a measure requires it.
    5. Instantiate: name responsibilities, define the interface the tactic needs, and say what data that element owns.
    6. Sketch the view that shows the tactic, and record the decision with its tax.
    7. Review: does this iteration's goal now have a response you believe, and which scenarios remain unaddressed?

    Stop when the purpose of the iteration is met. Continue in a later iteration for the next driver. Teams that try to satisfy latency, availability, security, and modifiability in one picture produce a diagram of every tactic they have heard of. The unaddressed scenarios should be visible as a list, not hidden behind extra boxes.

    Cost, observability, and testability as architectural qualities

    Cost is a quality attribute when a tactic has a bill: a second region, a premium database tier, a per-request tax-service call. Put the bill next to the scenario so "p95 under 200 ms" is not approved on a design that calls three vendors synchronously at a price the margin cannot carry.

    Observability is architectural when the response measure cannot be collected any other way. If the scenario says p99 at the API, the path needs a timer. If the scenario says "denied access is attributable," the log needs a principal and must not need a payload. Building the entire telemetry platform is out of scope here. Omitting the one instrument the scenario depends on means the scenario is theater.

    Testability is architectural when the response cannot be shown without a structure that supports it. An integrity scenario under concurrency wants a test that can force the race. A design whose balance update is buried in a controller and a static call cannot host that test. Lesson 4 and lesson 5 make that test possible. The driver is the scenario, not a preference for interfaces.

    Architecture and Design Diagrams

    The price-quote path and the capture path are different artifacts. Drawing them as one "checkout box" is how their scenarios get the same pool, the same timeout, and the same failure story.

    mermaid
    flowchart LR
    learner[Learner]
    subgraph quote [Price-quote path]
    apiQ[Quote API]
    cache[Price cache]
    tax[Tax rules module]
    end
    subgraph capture [Capture path]
    apiC[Capture API]
    db[(Entitlement and obligation)]
    vendor[Payments vendor]
    end
    learner --> apiQ
    apiQ --> cache
    apiQ --> tax
    learner --> apiC
    apiC --> vendor
    apiC --> db

    The cache is a tactic on the quote scenario only. The capture path does not read the cache to decide whether a payment was taken. That separation is the integrity scenario's artifact boundary. A reviewer who "simplifies" by pricing from the cache at capture time has merged two artifacts and dropped a scenario.

    ADD's single iteration fits on a flow. The loop is per iteration, not per meeting.

    mermaid
    flowchart TD
    inputs[Brief plus concrete scenarios]
    goal[Select one iteration goal]
    element[Choose the element to refine]
    tactic[Choose tactics and accept the tax]
    instantiate[Name responsibilities and data ownership]
    record[Sketch the view and record the decision]
    review{Goal met?}
    inputs --> goal --> element --> tactic --> instantiate --> record --> review
    review -->|remaining drivers| goal
    review -->|purpose met| done[Stop this iteration]

    A latency scenario fails closed when the downstream tax call exceeds its budget. The sequence is the design, not an error-handling afterthought.

    mermaid
    sequenceDiagram
    participant Learner
    participant QuoteAPI
    participant Tax as Tax module
    Learner->>QuoteAPI: Price quote
    QuoteAPI->>Tax: Rate for country, bounded
    alt Response inside budget
    Tax-->>QuoteAPI: Rate
    QuoteAPI-->>Learner: Itemized total
    else Bound exceeded
    Tax--xQuoteAPI: Timeout
    QuoteAPI-->>Learner: Quote unavailable, nothing reserved
    end

    The response "nothing reserved" matters. A timeout that still creates a pending order has changed the function. Availability of the quote and integrity of the order are different scenarios, and the sequence keeps them apart.

    Technical Explanation

    Measures that can be collected

    A response measure is a contract with an operator. Write it so a person who was not in the room can implement the check.

    Measure shapeUse whenReject when
    p95 and p99 over a window, at a named boundaryLatency of an interactive pathOnly a mean is offered, or the boundary is "the cloud"
    Error rate of a named function, plus a definition of errorAvailability of that functionThe load balancer's TCP check is the only signal
    Multiplier, resource added, quality that must holdScalability"Horizontal scale" with no state story
    Deny or allow, with an audit field listSecurity resist and accountability"Compliant" or "encrypted" with no artifact
    Modules touched, and a time box for a competent authorModifiability"Easy to change" or a slogan about cleanliness
    Currency per month at a named loadCostThe tactic is approved with no bill

    Windows matter. A p95 over five seconds can be gamed by a quiet minute. A p95 over a day hides a bad hour. Match the window to the stakeholder's pain. Enrollment week needs the hour of the peak. A retention scenario needs the years in the schedule, which you cannot "meet" in a dashboard tile; you meet it with a store and a test of restore.

    Tactics and the tax they usually charge

    Use this table in review when someone proposes a mechanism. If the tax column is blank in their write-up, the proposal is incomplete.

    TacticTypically helpsTypically taxes
    Bound a downstream callLatency tail, availability of the callerFunctionality during dependency failure; may increase visible errors
    Separate resource poolsIsolation of a path's latency and availabilityCapacity planning; a quiet pool looks wasteful
    Cache a read modelLatency and read scalabilityFreshness, memory, invalidation complexity, and a new failure when the cache is wrong
    Redundant stateless instancesAvailability of the process, scale outCost, and any state you forgot was in memory
    Replica databaseRead scalability, failover optionsReplication lag, split-brain risk, operational skill
    Authorize in the data ownerSecurity, especially tenant isolationA call or a join the edge cannot skip; latency
    Hide a rule behind an interfaceModifiability of that ruleIndirection, and a temptation to abstract the second rule before it exists
    Defer binding to configurationSpeed of a class of changeIntegrity and audit of who changed the value
    Synchronous vendor call inside the user requestSimplicity, immediate truthLatency budget, availability coupling

    None of these tactics is a default. The scenario selects. Two scenarios can select opposite tactics, which is the moment ADD earns its keep: you refine two elements instead of compromising both in one box.

    Freshness, consistency, and the cache

    A price cache improves the quote scenario and can damage a learner who must be shown a total the capture will honor. The architectural options, each a different trade-off:

    • Quote and capture both compute from the source of truth. Best integrity, worst coupling of quote latency to the tax and price store.
    • Quote uses a cache with a time-to-live; capture recomputes and refuses to capture if the total moved. The learner may see a "price changed" response. Product must accept that response. The scenario for integrity stays intact.
    • Quote and capture both trust the cache. Fast, and wrong when the cache is stale or poisoned. This meets a latency scenario by deleting an integrity scenario. Only a stakeholder who owns both may accept it, and they must do so in the decision record.

    TechLearningPro's marketplace uses the middle option for course prices. A sale banner is allowed to be seconds stale. A charge is not. The decision is dull and correct. Dull is what a tactic looks like when the scenario did the work.

    Availability arithmetic without self-deception

    If a user-visible function needs the API, the database, and the identity provider, the function's availability is bounded by the combination, not by the best component. You do not multiply marketing nines. You write the dependency in the environment slot of the scenario: "IdP healthy" is an environment, and a second scenario should say what the response is when the IdP is not. Lesson 1's Harborline brief did this in prose. The scenario form makes it harder to skip.

    Error budgets are a way to spend unavailability on purpose: releases, experiments, chaos tests. They belong to the SLO lesson. Architecturally, the point is that a target of "always" leaves no legal window for the tactics (deploy, restart, failover rehearsal) that keep the target honest.

    Security placement

    Encryption in transit does not locate authorization. The structure question is which component knows the object and the principal together. For TechLearningPro progress, that component is the progress owner, not the catalog edge and not the object store. A signed URL that lets a player fetch video can be a good latency and scale tactic and a bad security scenario if the URL's lifetime and scope are "forever" and "any object." The scenario writes the lifetime down. The tactic (short-lived, prefix-scoped URL minted by the platform) exists to meet the measure. The cost is extra calls to mint URLs, which is a latency tax you can measure.

    Logs are a security artifact. A scenario that says "denied attempts are attributable" and a log pipeline that stores full request bodies will fail a different security scenario about confidentiality. One instrument often serves one scenario and injures another. That is a trade-off point: a property of the design that affects more than one quality in opposite directions. Lesson 3 uses the term in utility trees. You can already mark it when you see a log field that both helps audit and leaks a learning history.

    Modifiability versus delivery speed

    A tactic that introduces an interface, a module split, or a new deployable spends time now to reduce time later. The spend is only rational when the scenario's change class is real. "We might add a second tax engine" is not a stimulus. "Finance will change a country rate twice a quarter, and legal will add a country this year" is a stimulus. Lesson 4 is the class-level version of this judgment. If you feel the pull to abstract during this lesson's exercise, write the scenario first. If the source of the stimulus cannot be named, do not pay the tactic.

    What ADD will not do

    ADD will not invent missing business goals, and it will not make a constraint disappear. If the only database you may operate is one PostgreSQL instance, "add a read replica in another region" may be an illegal tactic until the constraint is escalated. Record the iteration as blocked on a constraint, not as a failed imagination exercise. ADD also does not rank the drivers for you. Ranking is the utility tree in the next lesson. When two drivers conflict inside one iteration, you have selected too many drivers or you have found a real trade-off. Trade-offs get a decision record. Hidden conflicts get an incident.

    Practical Implementation or Design Walkthrough

    Problem

    TechLearningPro's marketplace needs a price quote and a capture for a course cart. The brief from lesson 1 already names finance, the learner, and the catalog squad. This walkthrough runs one ADD iteration and leaves a validator that rejects scenarios the review cannot test.

    Inputs

    Assumptions, labeled: carts hold at most 20 course lines; midday steady load is the environment for the quote scenario; enrollment-week peaks are a separate scenario we do not try to satisfy in this iteration; card data stays at the vendor; one squad operates the system.

    Drivers selected for this iteration:

    • Q-quote. Source: signed-in learner. Stimulus: price quote. Artifact: quote path. Environment: steady load, tax data warm, vendor not called. Response: itemized total, nothing reserved. Measure: p95 under 200 ms and p99 under 500 ms at the API over a 15-minute window.
    • Q-capture. Source: the same learner. Stimulus: pay. Artifact: capture path. Environment: vendor accepts the charge. Response: entitlement and payout obligation commit, or neither does; email is outside the transaction. Measure: zero captured charges without an entitlement row in a reconciliation sample, and the inverse.
    • Q-tax. Source: catalog developer. Stimulus: change one country's VAT rate. Artifact: tax module. Environment: production capture code. Response: no edit to the vendor adapter. Measure: diff limited to the tax module and its tests, merged in one working day.

    Out of this iteration: multi-region, coupon stacking, instructor-led private cohorts. They stay on the unaddressed list.

    Iteration

    Element to refine: the quote path first, because Q-quote and Q-tax meet there, and Q-capture must not be loaded with quote tactics.

    Tactics chosen:

    • Tax rules sit behind a TaxPolicy interface in the catalog module. This is the modifiability tactic for Q-tax. The tax is indirection the squad accepts because the stimulus is on the calendar, not hypothetical.
    • The quote path calls TaxPolicy in process. It does not call the payments vendor. Q-quote's environment says the vendor is not on this path, which removes a latency dependency by structure.
    • A bounded computation: tax lookup is local. There is no remote call to time out on the quote path in this iteration. If a later iteration puts a remote tax engine behind the same interface, the scenario must gain a bound and a failure response. The interface is what makes that iteration possible without rewriting capture.
    • Capture recomputes the total via the same TaxPolicy and refuses the charge when the recomputed total differs from the quoted total by any amount. The learner sees "price changed." Finance prefers that to a stale charge. Product accepted the response. This is the middle cache option, except the first iteration does not even cache: the local policy is cheap. A cache is deferred until a measure says the local policy misses p95. That deferral is a decision.

    Data ownership: the tax module owns rate tables. The capture path owns the entitlement row and the payout obligation. The quote path owns nothing durable.

    Consequence: Q-quote can be met only while TaxPolicy stays in process and cheap. The reopening condition is a p95 above 200 ms for a week of steady traffic with the policy as the hot span. Then, and only then, a cache is an allowed tactic, with a new scenario for "quoted total may be at most N seconds stale, and capture still recomputes."

    Scenario records

    The validator is the review gate. A scenario must fill every slot, and the measure must contain a digit. Latency measures must mention a percentile token (p95, p99, or p50) so "200 ms" alone cannot pass as a latency scenario. This is a guild rule, not a standard. It encodes the lesson. It will reject some clever measures; a human can waive it in the decision record, not by weakening the check in silence.

    java
    package com.techlearningpro.architecture.quality;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Locale;
    import java.util.regex.Pattern;
    public record QualityScenario(
    String id,
    String attribute,
    String source,
    String stimulus,
    String artifact,
    String environment,
    String response,
    String responseMeasure) {
    public QualityScenario {
    require(id, "id");
    require(attribute, "attribute");
    require(source, "source");
    require(stimulus, "stimulus");
    require(artifact, "artifact");
    require(environment, "environment");
    require(response, "response");
    require(responseMeasure, "responseMeasure");
    }
    private static void require(String value, String field) {
    if (value == null || value.isBlank()) {
    throw new IllegalArgumentException(field + " is required");
    }
    }
    }
    public record ScenarioFinding(String code, String message) {}
    public final class ScenarioReview {
    private static final Pattern DIGIT = Pattern.compile(".*\\d.*");
    private static final Pattern PERCENTILE = Pattern.compile(".*\\bp9\\d\\b.*|.*\\bp50\\b.*");
    public List<ScenarioFinding> review(QualityScenario scenario) {
    var findings = new ArrayList<ScenarioFinding>();
    if (!DIGIT.matcher(scenario.responseMeasure()).matches()) {
    findings.add(new ScenarioFinding(
    "MEASURE_WITHOUT_NUMBER",
    scenario.id() + " measure has no number"));
    }
    if (scenario.attribute().toLowerCase(Locale.ROOT).contains("latency")
    && !PERCENTILE.matcher(scenario.responseMeasure().toLowerCase(Locale.ROOT)).matches()) {
    findings.add(new ScenarioFinding(
    "LATENCY_WITHOUT_PERCENTILE",
    scenario.id() + " latency measure names no percentile"));
    }
    if (scenario.artifact().equalsIgnoreCase("the system")) {
    findings.add(new ScenarioFinding(
    "UNBOUNDED_ARTIFACT",
    scenario.id() + " must name a path or module, not the whole system"));
    }
    return List.copyOf(findings);
    }
    }

    Timing the path the scenario names

    Micrometer's Timer is the instrument that matches Q-quote. Spring Boot exposes a MeterRegistry when Actuator is on the classpath (spring-boot-starter-actuator). publishPercentiles configures percentile publication for that timer. Histograms and the export format (Prometheus, CloudWatch, or another registry you actually run) are an operational choice left to the platform; the architectural obligation is that the timer wraps the quote, not a health endpoint.

    java
    package com.techlearningpro.marketplace.quote;
    import io.micrometer.core.instrument.MeterRegistry;
    import io.micrometer.core.instrument.Timer;
    import java.util.function.Supplier;
    import org.springframework.stereotype.Component;
    @Component
    public class PriceQuoteProbe {
    private final Timer quoteTimer;
    public PriceQuoteProbe(MeterRegistry registry) {
    this.quoteTimer = Timer.builder("marketplace.price_quote")
    .description("Latency of the price-quote path named in Q-quote")
    .publishPercentiles(0.95, 0.99)
    .register(registry);
    }
    public <T> T record(Supplier<T> quote) {
    return quoteTimer.record(quote);
    }
    }

    Timer.record(Supplier) times the supplier and returns its value. It does not swallow exceptions. A quote that fails still counts in the timer, which is what you want when you are looking at the tail. If you need success and failure as separate series, add a tag with Timer.builder(...).tag("outcome", "success") on two timers. Tags are Micrometer's real API. Do not invent a parallel metrics facade.

    A budget check used in tests and in a review harness compares one observation to the scenario. It is not a percentile calculator. Percentiles come from the registry over the window. The harness stops a unit test from calling a 2-second stub and pretending Q-quote is met.

    java
    package com.techlearningpro.architecture.quality;
    import java.time.Duration;
    public record LatencyBudget(String scenarioId, Duration p95, Duration p99) {
    public boolean sampleWithinP95(Duration observed) {
    return !observed.isNegative() && observed.compareTo(p95) <= 0;
    }
    }

    Comparing one sample to a p95 budget does not prove the percentile. The method name says sampleWithinP95 so nobody reports a single green test as the response measure. The measure remains "p95 over 15 minutes at the API." The unit test proves only that a known slow stub is detectable.

    Tax policy as the modifiability tactic

    java
    package com.techlearningpro.marketplace.tax;
    import java.math.BigDecimal;
    public record Money(BigDecimal amount, String currency) {
    public Money {
    if (amount == null || currency == null || currency.isBlank()) {
    throw new IllegalArgumentException("amount and currency are required");
    }
    if (amount.signum() < 0) {
    throw new IllegalArgumentException("negative amounts are not money in this slice");
    }
    }
    }
    public interface TaxPolicy {
    /** Rate for a country code, applied to a net course price. Local and side-effect free. */
    Money gross(String countryCode, Money net);
    }

    A capture service depends on TaxPolicy, not on a table layout. The VAT change for Q-tax edits an implementation and its tests. The vendor adapter is a different class that never imports the rate table. That is the whole modifiability claim. Lesson 4 will press on whether this interface is justified and how far to split implementations. Here the interface exists because Q-tax's stimulus is on the calendar.

    What would fail review

    A proposal that puts the vendor call inside gross fails Q-quote's environment and Q-tax's measure in one move. A proposal that caches the gross price and captures the cached amount fails Q-capture. A proposal that adds a second region to "improve availability" in this iteration has changed the iteration goal without a scenario and without an operator. The review outcome is "not this iteration," which is a successful ADD result.

    Enterprise Real-World Scenarios

    The quote that became a charge

    During a mid-year price change, TechLearningPro's catalog updated a course from 49 to 59 currency units while learners still had the old quote open in a browser tab. The first implementation captured whatever total the browser sent. Finance found a cluster of undercharges. Support found learners who had been overcharged by a stale tab in the other direction, because a different bug trusted the browser when it was higher too.

    The scenario that should have existed before the sale is Q-capture as written above: the artifact is the capture path, the response is "charge the recomputed total or refuse," and the measure is reconciliation, not "the API returned 200." The tactic is recomputation in the data owner. The tax is a visible "price changed" response and a small amount of extra work on a path that is already waiting on a vendor.

    The latency scenario was allowed to keep its cache only after this split. Quote pages still use a short-lived cache. Capture does not. The trade-off point is the cache itself: it helps Q-quote and hurts Q-capture if it is allowed to cross the boundary. The decision record says the cache is not an input to capture. The incident has not recurred for that class of bug. Neighboring bugs, such as a wrong VAT table, are Q-tax's problem and show up as a diff in one module, which is the modifiability measure working as a cost bound rather than as a compliment.

    Playback start versus a second region

    Instructors asked for video start under two seconds for learners in a second continent. A reference diagram appeared with an active-active platform in two regions. The squad that would operate it is the same squad that owns checkout. The planning numbers they already had: most concurrent viewers are in one region; the long start times were on a cold cache at the media edge, not on the progress API; progress writes must stay ordered for a given learner.

    Two scenarios made the diagram shrink.

    • Start-up latency. Source: a learner pressing play. Stimulus: first segment. Artifact: the media edge in front of object storage. Environment: warm cache for popular courses, cold cache allowed for the long tail. Response: first segment bytes. Measure: p95 under 2 seconds at the edge for the top decile of courses, measured at the edge, not at the progress API. Tactic: cache popular objects at the edge. Tax: storage bill and the chance a just-published cut is invisible until the edge TTL. Product accepted a publish delay measured in minutes for the long tail of edits, not for the first publish of a new lesson, which bypasses the TTL by an explicit purge. The purge is a second, smaller scenario.
    • Progress integrity. Source: the same learner. Stimulus: checkpoint. Artifact: the progress store in the home region. Environment: the edge is remote and may be stale. Response: checkpoint commits in the home region only. Measure: a replay of checkpoints for one learner is ordered. Tactic refused: writing progress in whichever region is nearest. Tax of the refusal: a checkpoint from another continent pays a cross-region round trip. That tax was acceptable because the stimulus rate is low next to video bytes. The active-active diagram failed this scenario by making two writers.

    Cost sat in the decision as its own line: edge cache for the top decile had a bill the content margin could carry; a second full platform region did not, and it violated the staffing constraint from lesson 1. Scalability of video bytes was solved at the edge. Scalability of progress writes was explicitly not the problem. Mixing them is the thinking error this lesson exists to prevent.

    Best Practices

    • Write the six slots even when you are in a hurry. An empty environment slot is a finding, not a footnote.
    • One scenario, one artifact. If you cannot point at a module or a path, you are not ready to pick a tactic.
    • Put percentile and window in every interactive latency measure, and say which boundary the clock uses.
    • Define "down" for the function. Do not inherit the load balancer's definition.
    • State the growth multiplier and the resource you will add before you say "scale out."
    • Place authorization in the component that owns the data. Note encryption as a path property, not as the security design.
    • For modifiability, name the change and the files it may touch. If the change is imaginary, do not buy the abstraction yet.
    • Record the tax in the same paragraph as the tactic. An unspoken tax returns as an incident with a surprised stakeholder.
    • Instrument the artifact the scenario names. A timer on a different route is a different claim.
    • Leave unaddressed scenarios on a list. A diagram that tries to show all of them has stopped being a design and become a poster.
    • When a reference architecture appears, ask which of its tactics match a scenario you wrote, and which taxes you did not budget.
    • Revisit measures after the first real peak. A scenario that cannot be wrong will not be used.

    Common Mistakes and Anti-Patterns

    The mean as the measure. Averages flatter a path whose tail is the support queue. Teams declare victory at 80 ms average while p99 sits at multiple seconds during the only hour that matters. The repair is p95 and p99, plus a window that includes the peak. Keep the average if you like. Do not let it be the contract.

    Nines without a function. "99.99 percent" copied from a vendor page becomes the platform target. The vendor measured its API. You depend on the vendor, the database, and the IdP. Learners measure whether play and pay work. The repair is a scenario per function, with the dependency written in the environment, and a second scenario for the dependency being down.

    Scalability as a vibe. The diagram gains instances, a bus, and a shard key because growth is expected someday. No multiplier, no saturated element, no quality that must hold. The repair is the scalability definition in this lesson. If the current node has headroom against the multiplier you can defend, say so, and keep the shard key in the unaddressed list.

    Security theater at the edge. HTTPS, a login page, and a penetration test the week before launch. The progress of another learner is still readable by changing an id, because authorization was never a scenario with an artifact. The repair is one deny-scenario per sensitive object type, tested in the component that owns the row. Lesson 63 expands the set. One real scenario beats a slogan.

    The cache that became the ledger. A cache introduced for a read scenario is later read by the write path because it is convenient and already populated. Integrity scenarios die in that convenience. The repair is data ownership in the ADD instantiation step: the cache owns a disposable read model; the capture path owns the charge decision; the code does not offer a constructor that takes a cached total as an authority.

    Modifiability bought before a change exists. Interfaces, plugins, and configuration for a second payments vendor, a second region, and a rules engine, in a product that has one vendor and one tax table. Delivery slows, and the first real change still ripples because it was not the change that was abstracted. The repair is Q-tax's discipline: a named source, a named stimulus, a bound on the diff. Lesson 4 shows the same discipline at class granularity.

    Tactics stacked in one iteration. Retries, a queue, a cache, a replica, and a new service arrive together "for resilience." Each one taxes a different scenario, and nobody can say which goal the iteration had. The repair is ADD's rule: a few drivers, one element, the taxes written down, the rest of the drivers left visible and unaddressed.

    Troubleshooting and Design Review

    Symptom: The dashboard is green and the sale hour was not.

    Likely cause: the timer is on a cheap path, the window excludes the peak, or the contract is a mean. The scenario's artifact is not what the graph shows.

    Investigation: read the response measure. Find the route and the clock boundary. Compare tags and the time range to the sale.

    Corrective action: move the timer onto the artifact. Change the contract if the boundary was wrong. Do not add a second cache until the graph matches the scenario.

    Symptom: Adding API instances did not move p95.

    Likely cause: the saturated element is not the API. Typical owners are a shared pool, a lock, a single database, or a downstream call with no bound.

    Investigation: a trace or a span around TaxPolicy and the vendor client, plus pool metrics. This is still architectural diagnosis, not a full observability design.

    Corrective action: a tactic on the saturated element (bound, isolate, cache the read, or scale that element). Scaling the unsaturated element is how the bill grows without the measure moving.

    Symptom: Two scenarios demand opposite designs of the same data.

    Likely cause: one artifact is being asked to serve both, or a trade-off point was never named. Quote freshness and capture integrity are the usual pair.

    Investigation: list data owners. If a cache is an input to a charge, the instantiation step was skipped.

    Corrective action: split the paths. Record the user-visible response ("price changed"). If product refuses that response and refuses staleness, the honest outcome is to recompute on both paths and relax the latency measure.

    Symptom: A modifiability claim and a one-line tax change edits twenty classes.

    Likely cause: the interface was drawn and the rate table is still read from controllers, email templates, and the vendor adapter.

    Investigation: search for the rate source. Compare the diff to Q-tax's measure.

    Corrective action: move reads behind the policy and delete the other reads. If the vendor must know the gross amount, pass money, not a rate lookup, across that boundary.

    Symptom: Availability work is proposed as a second region while the function's only outages are bad deploys.

    Likely cause: the scenario did not say what "down" means or what the recent stimulus actually was. A region tactic does not fix a migration that locks the table.

    Investigation: the last three user-visible failures and their causes.

    Corrective action: a tactic matched to the stimulus (expand-contract for schema, a bound on startup, a restore rehearsal). Keep the region on the unaddressed list until a scenario's environment is "loss of a region," which this squad may not be staffed to meet. Say that in the review.

    Design review questions for this lesson, after lesson 1's brief has passed: Is every selected driver a six-part scenario? Does the measure have a percentile or an equivalent concrete check? Is the artifact smaller than "the system"? Which tactic was chosen, and which tax did the stakeholder accept? Which scenarios were left unaddressed on purpose? Which instrument will prove the measure after launch?

    Interview Questions and Answers

    Q1. What are the six parts of a quality attribute scenario, and which part do candidates most often skip?

    Source, stimulus, artifact, environment, response, and response measure. The part most often skipped is the environment, followed by a measure that is only an adjective. Without the environment, a latency number from an idle laptop is treated as a production claim. Without an artifact, the team optimizes "the system" and instruments a health check. I write all six even for a rough scenario, and I label numbers that are still assumptions.

    Q2. Why is a mean a poor contract for an interactive path?

    The mean can sit comfortably inside a budget while a minority of calls, the ones that hit locks, cold caches, or retries, are slow enough to create support load. Learners remember the tail. I contract p95 and p99 over a window that includes the peak, and I say whether the clock is at the API or the client. I may still watch the mean as a diagnostic. I do not let it approve a design.

    Q3. How do you define availability so the number cannot be gamed?

    I name the function, the user-visible failure, the dependency environment, and the window. "The price-quote path returns a total or a controlled 'unavailable' within its latency budget, measured by that path's error and timeout rate, not by TCP open." I include the dependencies in either the environment or a paired scenario for their failure. I do not multiply a vendor's advertised nines by our own. Recovery belongs in the response: what a restart does to committed work.

    Q4. What is the difference between latency and scalability?

    Latency is the time of one interaction on one path. Scalability is the ability to take a stated workload multiplier by adding a stated resource while a stated quality, often latency or error rate, stays inside its measure. A system can have excellent latency at today's load and no legal way to add a second node because of in-memory session state. A system can scale out and still miss a latency budget if each request does more work. I ask for the multiplier and the saturated element before I discuss sharding or extra instances.

    Q5. How do you write a security scenario that is architectural rather than a slogan?

    I name a source (who is calling), a stimulus (the abuse or the mistake), an artifact (the component that owns the data or the secret), an environment (token lifetime, production), a response (deny, record, contain), and a measure a test can fail (the other learner's progress is not in the body; the log has a principal and no video title list). "We use HTTPS" can be one tactic inside a scenario about eavesdropping on a path. It is not a scenario about authorization. Placement of the allow-or-deny decision is the structure.

    Q6. What is a tactic, and how is it different from a pattern?

    A tactic is one design decision that influences a quality response: bound a call, separate a pool, authorize in the owner, hide a rule behind an interface. A pattern packages tactics and responsibilities, and it brings a known tax. A cache packages "keep a copy" and taxes freshness. I want the scenario and the tactic before the pattern name. In an interview I would rather hear "we bounded the tax call and failed the quote with nothing reserved" than a list of pattern names with no measure.

    Q7. Walk through one iteration of attribute-driven design.

    Confirm inputs, select a few drivers as the iteration goal, choose the element to refine, pick tactics and accept their taxes, instantiate responsibilities and data ownership, sketch the view, record the decision, and check whether the goal is met. Leave other drivers on a visible list. For a checkout I would refine the quote path against latency and tax-change modifiability, keep capture as a separate artifact that recomputes the total, and refuse a cache on the capture path. If I cannot name the tax, the iteration is not done.

    Q8. When does a cache hurt, and how do you decide to add one?

    A cache hurts when a stale or wrong copy is treated as authority for a decision that needed the source, and when invalidation becomes a second system nobody operates. I add one when a scenario's measure is missed and a profile shows the uncached work as the cause, and when a stakeholder has accepted the freshness bound. On TechLearningPro quotes, a short-lived cache is allowed. On capture, the total is recomputed. The cache is a trade-off point: it helps one scenario and is forbidden from crossing into the other.

    Q9. A stakeholder asks for five nines and active-active in two regions. What do you ask?

    I ask which function, what counts as down, which stimulus in the last incidents actually required a second region, who operates failover, and what the bill and the consistency story are. I often find the pain is deploy and schema risk, or a cold media cache, which are different tactics. If the staffing constraint is one squad, I say a second full region is a constraint escalation, not a diagram tweak. I will still write the scenario they want so the refusal is about a measure and a cost, not about attitude.

    Q10. How do modifiability scenarios keep a design from over-abstracting?

    They demand a real source and a real change, plus a bound on what the change may touch and how long it may take. "Might need another vendor" does not qualify. "VAT for one country changes, and the vendor adapter must not" does, and it justifies a narrow interface. If the next change we fear is unspecified, the fastest design is the concrete one, with a reopening condition when the second variation appears. That judgment is the bridge to SOLID in lesson 4. Architecture pays for flexibility where a scenario dates the stimulus.

    Q11. What is a trade-off point?

    A property of the design that moves at least two quality attributes in opposite directions. The price cache is one: lower quote latency, higher risk to capture integrity if the write path trusts it. A verbose audit log is another: better accountability, worse confidentiality and cost. I mark trade-off points in the decision so a later "small improvement" to one side has to reopen the other scenario. Sensitivity points, where one property dominates a single quality, are the siblings lesson 3 will put on a utility tree.

    Q12. How would you prove a latency scenario after launch without pretending a unit test is a percentile?

    I put a Micrometer timer on the route the artifact names, publish the percentile the contract uses, and read it over the window during a real or replayed peak. A unit test may show that one sample exceeded a LatencyBudget, which is a guard against a stub that sleeps. I label that test as a sample check. The response measure stays the registry's percentile at the boundary the scenario named. If I cannot collect it, the scenario is not yet operational, and the design is not yet evaluated.

    Hands-On Exercises

    Exercise: Quality scenarios for a commerce checkout

    Problem statement. Write the scenarios that would drive one ADD iteration for a course-commerce checkout: price quote, capture, and one modifiability change. Use your own numbers. Label assumptions.

    Tasks.

    1. Write three concrete scenarios in all six slots. One must be latency, one integrity or availability of capture, one modifiability.
    2. Add a security scenario for "a learner submits another learner's cart id."
    3. Mark one trade-off point and say which two scenarios it couples.
    4. Run one ADD iteration in writing: goal, element, tactics, data ownership, tax, reopening condition. Leave at least two drivers unaddressed on purpose.
    5. Specify the instrument that proves the latency measure, including the boundary of the clock.
    6. Describe one proposal you reject (second region, capture-from-cache, or tax logic inside the vendor adapter) and the scenario that rejects it.
    7. Note what your design does when the payments vendor exceeds a bound you chose.

    Expected deliverables. The scenarios, the iteration note, a small diagram of quote versus capture, and the rejection paragraph.

    Evaluation rubric.

    Assessment areaWeightWhat "good" looks like
    Scenario completeness25%Six slots, concrete artifact, numeric measure, explicit environment
    Tactic and trade-off25%Each tactic names the quality it taxes; one trade-off point is explicit
    ADD discipline20%One iteration goal; unaddressed drivers remain visible
    Integrity of capture15%A stale read cannot authorize a charge unless a stakeholder accepted that in writing
    Evaluation15%The instrument matches the artifact and the window

    A ScenarioReview finding on your latency scenario means the exercise is not ready for a human review. An empty finding list means it is ready, not that the numbers are correct.

    Lesson Summary

    Architectural thinking attaches every structure to a response, a condition, an element, and a tax. The six-part scenario is the form that makes the attachment reviewable. Latency wants percentiles and a clock boundary. Availability wants a function and a definition of down. Scalability wants a multiplier and a resource. Security wants a placement for allow-or-deny. Modifiability wants a real change and a bound on the diff.

    Tactics are the smallest design moves that influence those responses. Patterns package them and bring taxes. ADD uses a few scenarios per iteration so the diagram does not become a catalog of mechanisms. The quote path and the capture path in the TechLearningPro marketplace are the worked example: a cache may help one and is forbidden from deciding the other.

    Lesson 3 takes a pile of scenarios and decides which ones are architecture drivers, which are constraints in disguise, and how a utility tree keeps a review from treating every quality as equally urgent.

    Further Reading

    Ready to mark this lesson complete?Track your journey across the entire course.