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

    Requirements and Architecture Drivers

    A backlog is not an architecture input. TechLearningPro Academies, the multi-tenant product sold to employers, accumulated two hundred stories before anyone could say which six of

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

    Requirements and Architecture Drivers

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

    Lesson Overview

    A backlog is not an architecture input. TechLearningPro Academies, the multi-tenant product sold to employers, accumulated two hundred stories before anyone could say which six of them would force a structure. The stories were real: roster import, a custom course deadline, a logo on the certificate, a requirement that one customer's administrator must never read another's employees. They were treated as equal rows. The logo and the isolation rule consumed the same planning ceremony. Only one of them changes the data boundary.

    This lesson sorts inputs into architecture drivers. A driver is a business goal, a requirement, or a constraint that the design must satisfy and that is likely to shape a structure. Most functional requirements are not drivers. They are work to do inside a structure the drivers have already chosen. The sorting tool is a utility tree: quality refinements, concrete scenarios from lesson 2, and a pair of priorities (how much the business cares, how hard the scenario is to achieve). Constraints sit beside the tree, not inside it. A constraint is not prioritized. It is obeyed or escalated.

    You will leave able to write an architecture-driver document for a multi-tenant academy product, to tell a constraint from a requirement from an assumption, and to point at the scenarios that deserve the next ADD iteration. Workload shape (peak, steady, batch) is part of the document because an environment slot with no profile is a scenario you cannot test. Lesson 24 does the arithmetic of capacity. This lesson decides which numbers are allowed to drive design, and which are still guesses with owners.

    The enterprise-architecture primer discusses quality language at /learn/enterprise-architecture/quality-attributes. Design principles at /learn/design-principles tell you how to shape code once a driver exists. Neither course builds the tree or the driver record this review expects.

    Learning Objectives

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

    1. Classify an input as a business goal, a functional requirement, a quality-attribute requirement, a constraint, an assumption, or a risk.
    2. Decide whether a requirement is architecturally significant, using structure, cost of change, and stakeholder harm as the tests.
    3. Build a utility tree with business and technical priorities and select the next iteration's drivers from it.
    4. Separate constraints, which are not traded away in a backlog refinement, from requirements, which can be scoped.
    5. Write a workload profile (peak, steady, batch) that can fill the environment slot of a scenario.
    6. Name sensitivity points and trade-off points in a driver document so later design moves cannot ignore them.
    7. Produce a driver record a Spring configuration can carry for the assumptions that are still numeric guesses.

    Prerequisites

    • Lesson 1's brief: stakeholders, sourced constraints, and the idea that some choices are expensive to undo.
    • Lesson 2's six-part scenarios. This lesson prioritizes scenarios. It does not re-teach percentiles.
    • Enough Java 21 to read records and a Spring Boot 3 @ConfigurationProperties type. The workload numbers live in configuration so a guess can be changed without a code edit, and so the guess stays labeled.
    • No prior ADD workshop. One iteration from lesson 2 is enough context.

    Detailed Core Concepts

    What a driver is

    An architecture driver is any input that both matters to the success of the system and is likely to influence a structure, a tactic, or a binding constraint on those structures. The Software Engineering Institute groups the usual inputs as:

    • Business goals — why the system is being funded, in language a sponsor will recognize. "Sell Academies to two hundred employers without a custom deploy per employer" is a goal. It is not yet a schema.
    • Functional requirements — what the system must do. Only some of them are drivers.
    • Quality-attribute requirements — the concrete scenarios of lesson 2.
    • Constraints — decisions already made or conditions imposed, with a source.

    Assumptions and risks travel with the driver document and are not themselves drivers. An assumption is a statement you are treating as true so that design can proceed ("peak roster import is ten thousand rows"). A risk is a way the design can fail if an assumption is false or a scenario is missed ("if a tenant admin token is scoped only by a client-sent tenant id, isolation fails"). When an assumption is confirmed or killed, the driver set may change. That is the lifecycle from lesson 1, applied to inputs.

    If everything is a driver, ADD has no iteration goal. The professional act is exclusion. A certificate logo is a functional requirement. It becomes a driver only if brand assets force a new storage boundary, a new authorization rule, or a publishing workflow with its own availability story. Otherwise it is a task.

    Architecturally significant requirements

    An architecturally significant requirement (ASR) is a requirement that is both important to stakeholders and unusually hard to satisfy inside the structures you already have. Difficulty is about structure, not about effort in the abstract. A screen that takes three weeks and touches one module is hard work and a weak ASR. A rule that two tenants must be unable to observe each other, including through logs, exports, and a careless tenant_id parameter, is an ASR even when the first test looks small.

    Three tests, applied in order:

    1. Structure. Satisfying it forces a new element, a new relation, a different data owner, or a different trust boundary. Isolation does. A logo upload into an existing media bucket, authorized as "this tenant's admin," usually does not.
    2. Sensitivity. A small change in a property (token contents, index choice, consistency of a read) would swing a high-priority scenario from pass to fail. Those properties are sensitivity points. Requirements that create them are ASRs.
    3. Harm. The stakeholder damage of getting it wrong is disproportionate to the size of the story. A cross-tenant roster leak ends the Academies contract. A mis-colored logo does not.

    Functional requirements that pass the tests are often the ones that define consistency boundaries ("import either commits the whole roster or leaves the previous roster in place"), identity ("tenants bring their own IdP"), or money ("a seat can be billed once"). The rest of the functional list is input to iteration planning inside the architecture, not input to the architecture itself.

    Quality-attribute scenarios are ASRs when their priority pair is high. A latency scenario nobody will pay to achieve is still a scenario. It is a low-priority leaf, not a driver of this iteration.

    Business goals before features

    Sponsors fund outcomes. Architects who start from features invent structures the outcome does not need, and they miss structures the outcome does. For Academies the goals the sponsor actually signed were:

    • G1. Close enterprise deals without a one-off codebase per customer.
    • G2. Keep each customer's employee data unreadable to other customers and to TechLearningPro staff who have no operational reason.
    • G3. Let a customer's own administrators finish a roster import on a business morning without a ticket.
    • G4. Stay operable by the current platform squad. A per-tenant deployable is not a goal. It is a contradiction of G1 and of the staffing constraint.

    G1 pushes toward one deployable and a tenant key inside the data model, or toward a small number of shared services. It pushes away from a repository fork. G2 pushes toward a non-optional tenant predicate in every query, export, and log. G3 pushes toward an import path whose failure leaves a usable previous roster. G4 pushes away from a design that needs a platform team to provision a database per tenant on the first contract.

    Notice what is absent. "Use microservices" is not a goal. "Be cloud native" is not a goal. Those phrases can be preferences of people in the room. They enter the document only as preferences, where they cannot outrank G2.

    A goal that cannot lose a feature argument will be ignored. Write goals so that a proposed structure can be shown to violate one. "Delight customers" cannot lose an argument. G2 can.

    Constraints versus requirements

    The distinction is the one teams blur when they want a preference to be immovable, or when they want a real constraint to be negotiable in grooming.

    RequirementConstraint
    OriginA stakeholder need this program can still scope, sequence, or rejectA condition already fixed, or imposed from outside the program
    PriorityCan be ranked, deferred, or cutNot ranked. Obeyed or escalated with eyes open
    Example"An admin can import a CSV roster of up to 10,000 rows""Employee data for EU tenants stays in the EU region named in the contract"
    Failure mode if misfiledA constraint treated as a story gets scheduled into a sprint and slippedA requirement treated as a constraint fossilizes a guess

    "We must use PostgreSQL" is a constraint when the company standard and the operated fleet say so, and the reversal cost is a new operational skill. It is a preference when the sentence begins "we always." "p95 of interactive roster search under 300 ms" is a requirement. You may relax the number with the stakeholder. You may not "relax" the data-residency contract because the sprint is full.

    Some statements are constraints on one dimension and requirements on another. "Tenants authenticate with their own IdP" may be a constraint for a signed customer (the contract names the protocol) and a requirement for the product (the design must be capable). Write both lines. The capability is the ASR. The named issuer for the first customer is a constraint on the first integration, not a reason to hard-code that issuer into the domain model.

    Lesson 1 asked for a source on every constraint. This lesson adds the consequence of violation and the escalation path. A driver document that lists constraints with no escalation path will be violated informally, in a pull request, by someone who believes they are negotiating a requirement.

    Assumptions and risks

    Assumptions keep the document honest when the number is not yet measured. Each assumption has an owner and a confirmation plan. "Tenants ≤ 200 in the first year" owned by the sponsor, confirmed at quarterly planning, is an assumption. The same sentence with no owner is a rumor the schema will be built on.

    Risks are the architectural ones, not the full project risk register. Good architectural risks name a scenario and a missing fact:

    • If any query path can omit the tenant predicate, G2 fails. Mitigation belongs in the structure (a repository that requires a tenant context) and in a test that fails the build when a query does not. The risk stays open until that structure exists.
    • If roster import is synchronous on a request thread, G3 fails at the row cap. Mitigation is a batch profile with a visible job state. The risk stays open until the workload profile says otherwise and a prototype proves it.

    When the mitigation is "we will be careful," the risk is unowned. Careful is not a structure.

    The utility tree

    A utility tree, as used in ATAM and in ADD, is a way to see quality priorities without pretending the qualities are independent. The root is utility: the goodness of the system to its stakeholders. The next level is the quality attributes that matter for this system. Under each attribute are refinements ("data isolation," "interactive latency," "import robustness"). Under each refinement are concrete scenarios. Each scenario carries two priorities, traditionally High, Medium, or Low:

    • Business importance — how much the sponsor and the affected stakeholder care if the scenario fails.
    • Technical risk — how hard it is to get right given the constraints, the team's experience, and the sensitivity points you already suspect.

    The scenarios you design first are the ones that are High/High, then High/Medium. Low/Low scenarios are real and should not be designed for in the same iteration. A tree that marks everything High is a tree that has not been facilitated. Someone has to say that certificate branding is Low technical risk and Medium business value, and that cross-tenant isolation is High/High.

    Facilitation is part of the technique. The architect does not assign business importance alone. The sponsor does, after the scenario is concrete enough to feel. The architect does own the technical-risk score, and must be willing to mark "easy" when it is easy so the tree does not inflate.

    A compact Academies tree:

    AttributeRefinementScenario (compressed)BusinessTechnical
    SecurityTenant isolationAdmin of tenant A requests a roster id minted for tenant B and receives neither rows nor a distinguishable "exists" responseHH
    SecurityStaff accessA support token without an explicit break-glass grant reads zero employee emailsHM
    ReliabilityImport atomicityA failed import of 10,000 rows leaves the previous roster queryableHM
    PerformanceInteractive readTenant admin searches employees, p95 under 300 ms at steady loadML
    ModifiabilityIdP per tenantAdd an OIDC issuer for one tenant without a code forkHH
    ModifiabilityBrandingReplace a logo and a color; no schema change to enrollmentLL

    The next ADD iteration takes isolation and per-tenant IdP. Import atomicity follows. Branding waits. Interactive search is watched and not allowed to justify a new datastore while the technical risk is low and the current indexes are unproven. That is the tree doing its job.

    Sensitivity points and trade-off points

    A sensitivity point is a property of one or more elements that is critical to achieving a particular quality response. For tenant isolation, the sensitivity point is "every read and every export is parameterized by a tenant context taken from the authenticated principal, never from an unverified body field." A design can look multi-tenant and fail at that single property.

    A trade-off point is a property that affects more than one quality in opposite directions. "Shared schema, shared database, tenant column" helps G1 and G4 (one thing to operate, no fork) and concentrates G2's risk into the predicate. "Database per tenant" helps blast-radius stories and hurts G4 immediately. Choosing the shared schema does not make the trade-off go away. It names the sensitivity point you must now test: the predicate.

    Write both into the driver document. Designers who only see requirements will "optimize" a trade-off point for the scenario they are holding and call the other scenario a regression.

    Workload profiles

    Scenarios need environments. Environments for this product come in three profiles, because Academies does not have one traffic shape.

    • Steady interactive. Tenant admins and learners browse and search during the working day. Reads dominate. The latency scenario lives here. Planning assumption for the first year: 200 tenants, a few dozen concurrent admin searches, learner traffic similar to the existing platform plus a tenant predicate.
    • Peak. Monday morning of a customer's launch week, or the hour a company assigns a required course. Writes of enrollment rows spike for one tenant while others stay at steady. A design that only models a global average will size a pool for nobody's actual Monday.
    • Batch. Roster import and nightly completion exports. Large, infrequent, allowed to take minutes, not allowed to starve the interactive pool. This profile is why the import scenario's artifact is a job, not the request thread that accepted the file.

    A profile states what you believe and the unit. It does not pretend to be lesson 24's capacity model. It is already strong enough to reject a design: an import that holds a database lock for the duration of 10,000 row upserts will violate the interactive scenario's environment ("import may be running"). That rejection is available before any load test.

    Prioritization is a decision

    Cutting a High/High scenario out of an iteration because the sprint is full is an architecture decision, not a grooming detail. Record it as a risk with a date. Promoting a Low/Low scenario into the design because it is technically interesting is also a decision, and it spends the iteration that isolation should have had. The utility tree makes both moves visible. A flat backlog does not.

    Stakeholders will ask for everything to be High. The facilitator's question is comparative: "If only one of isolation and branding can shape the schema this month, which failure do you want to explain to the customer?" Comparative questions produce priorities. Absolute questions produce stickers.

    Architecture and Design Diagrams

    The driver document sits in front of ADD. Constraints do not pass through the priority filter. Assumptions can be falsified and then re-enter as drivers or as risks.

    mermaid
    flowchart TD
    goals[Business goals]
    funcs[Functional requirements]
    scenarios[Quality scenarios]
    constraints[Constraints with sources]
    assumptions[Labeled assumptions]
    asr{Structurally significant or High priority?}
    tree[Utility tree]
    drivers[Iteration drivers]
    risks[Risks and escalation]
    goals --> asr
    funcs --> asr
    scenarios --> tree
    tree --> asr
    asr -->|yes| drivers
    asr -->|no| backlog[Backlog inside the structure]
    constraints --> drivers
    assumptions --> risks
    drivers --> risks

    Tenant isolation is a trust boundary, not a column name sprinkled in a diagram. The context view shows one Academies system because G1 forbids a deployable per customer. The external IdP is per tenant and still outside the system.

    mermaid
    C4Context
    title TechLearningPro Academies — context for drivers
    Person(admin, "Tenant admin", "Imports rosters and must not see other tenants")
    Person(learner, "Employee learner", "Sees only their employer's catalog assignments")
    Person(staff, "Platform staff", "Operates the system without standing access to employee mail")
    System(academies, "Academies", "Shared platform, tenant-scoped data")
    System_Ext(idp, "Customer IdP", "OIDC, one issuer per tenant")
    System_Ext(platformIdp, "TechLearningPro IdP", "Staff and break-glass")
    Rel(admin, academies, "Manages roster", "HTTPS")
    Rel(learner, academies, "Uses assigned courses", "HTTPS")
    Rel(staff, academies, "Operates", "HTTPS")
    Rel(academies, idp, "Authenticates tenant users", "OIDC")
    Rel(academies, platformIdp, "Authenticates staff", "OIDC")

    The workload profiles are three paths into one database. The batch path must not be drawn as just another request if the driver says interactive latency holds while an import runs.

    mermaid
    flowchart LR
    subgraph steady [Steady interactive]
    search[Admin search]
    end
    subgraph peak [Peak assignment]
    assign[Course assignment burst]
    end
    subgraph batch [Batch]
    job[Roster import job]
    end
    db[(Shared relational store with tenant key)]
    search --> db
    assign --> db
    job --> db

    The arrow from the job into the same store is the trade-off point. It satisfies "one thing to operate." It creates the sensitivity point around locks and around the tenant predicate. A diagram with three databases would be a different trade-off, and G4 would have to be escalated first.

    Technical Explanation

    A driver document, section by section

    The guild uses one document per initiative, short enough to review in a sitting. For Academies the sections are fixed so two initiatives can be compared.

    1. Purpose and sponsor. One sentence, and the name of the person who can accept a trade-off.
    2. Business goals. Numbered, each able to knock out a design.
    3. Stakeholders. Reuse lesson 1's table; add the tenant admin and the employee, who are not the same stakeholder as a consumer learner.
    4. Constraints. Statement, source, violation consequence, escalation path.
    5. Assumptions. Statement, owner, how it will be confirmed, date.
    6. ASRs. The functional ones that passed the three tests, written as behaviors with consistency and authorization included.
    7. Utility tree. The table, not a speech.
    8. Workload profiles. Steady, peak, batch, with units.
    9. Sensitivity and trade-off points. The properties later design reviews must not "clean up."
    10. Risks. Open items tied to a scenario id.
    11. Non-drivers. A short list of real requirements that will be built inside the structure and must not be allowed to reshape it without a new scenario. Branding lives here on purpose. The list prevents a hurt feeling from being confused with an omission.

    Section 11 is the one teams skip, and it is the one that keeps the next meeting honest.

    Tenant context as a sensitivity point

    The structural claim for G2 is: a tenant id used for authorization originates in the authenticated token, after the issuer for that tenant has been checked, and is carried in a type that repository methods require. A tenantId field on a JSON body is data the caller can set. It may match the token, and the server still ignores it for authorization. Logging includes the tenant id from the token and omits employee email on the default support path.

    This is still requirement-level. It does not choose between row-level security in PostgreSQL, a mandatory predicate in a repository class, or a schema per tenant. Those are tactics in a later ADD iteration, and they are not equal under G4. The driver document's job is to make a tactic that trusts the client-supplied id illegal against the sensitivity point, before anyone writes the repository.

    Discriminator leaks are part of the same scenario. A response that returns 404 for a missing id and 403 for another tenant's id tells the caller the id exists. The scenario in the tree asked for a response that does not distinguish. That single sentence deletes a common "secure enough" implementation. It is the kind of precision drivers exist to carry. A functional story that says "return an error if the roster is not ours" would have been implemented with 403.

    IdP per tenant without a fork

    G1 and the modifiability scenario agree: one codebase, many issuers. The ASR is the capability to register an issuer, audience, and certificate source per tenant, and to refuse a token whose issuer is not the one registered for the tenant key inside the token. The first customer's issuer name is a configuration constraint for that tenant, not a branch in source control.

    Hard-coding the first issuer fails the modifiability scenario and fossilizes a constraint into the design. Building a general identity broker before one issuer works fails the schedule and is not required by the scenario. The driver-level decision is the shape: issuer configuration is data, tokens are checked against that data, and the domain of "roster" never parses a JWT. Where the JWT is parsed is an interface decision for lesson 5's adapters. Naming it as a driver now stops the domain model from growing a dependency on a token library just because the requirement mentioned login.

    Import as a workload, not a request

    G3 plus the batch profile make a synchronous controller method an architecture bug, not a slow endpoint. The functional ASR: accepting a file creates a job record in the same tenant; the job's success replaces the roster in one commit; failure marks the job and leaves the previous roster as the one readers see. Duration is allowed to be minutes. The interactive pool is not the worker pool.

    That ASR does not choose a specific queue product. Lesson 33 is where queues are designed. At driver level the illegal options are already clear: holding the admin's HTTP request open for the whole parse, and taking a lock that blocks search for the tenant for the whole parse. A reviewer can reject those from the document without a proof of concept.

    Shared database as an explicit trade-off

    Write the trade-off in the sponsor's language. "We will run one relational database for Academies tenants this year because the squad can operate one database and G1 forbids a fork. The cost is that isolation is a property of every query. We will not accept a per-tenant database until either a contract's residency clause cannot be met by region-level placement of the whole database, or the blast radius of a single database becomes a failed High scenario. The sensitivity point we will test continuously is the tenant predicate and the origin of the tenant id."

    Region placement for EU data is a constraint that may force a second database later. It does not force a database per tenant. Mixing those two ideas is a common escalation error. The driver document separates "data in the EU" from "data in a private database," because they have different sources and different costs.

    Configuration for assumptions

    Assumptions that are numbers should not hide in a slide. A @ConfigurationProperties record makes the guess deploy-time data, documented, and overridable per environment. It does not make the guess true. The class exists so a review can ask "which profile is this environment claiming?"

    Spring Boot 3 binds this type when the application scans configuration properties (@ConfigurationPropertiesScan or @EnableConfigurationProperties). The prefix is part of the contract.

    java
    package com.techlearningpro.academies.drivers;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    @ConfigurationProperties(prefix = "academies.workload")
    public record AcademyWorkloadProperties(
    int tenants,
    int steadyAdminSearchesPerSecond,
    int peakAssignmentsPerMinutePerTenant,
    int maxImportRows,
    int importBudgetSeconds) {
    public AcademyWorkloadProperties {
    if (tenants < 1 || steadyAdminSearchesPerSecond < 1) {
    throw new IllegalArgumentException("tenant and search rates must be positive");
    }
    if (maxImportRows < 1 || importBudgetSeconds < 1) {
    throw new IllegalArgumentException("import bounds must be positive");
    }
    if (peakAssignmentsPerMinutePerTenant < 1) {
    throw new IllegalArgumentException("peak assignment rate must be positive");
    }
    }
    }

    An environment file then states the assumption in the open:

    yaml
    academies:
    workload:
    tenants: 200
    steady-admin-searches-per-second: 20
    peak-assignments-per-minute-per-tenant: 500
    max-import-rows: 10000
    import-budget-seconds: 900

    Changing the YAML is not a capacity study. It is a change to an assumption, which should update the driver document and, when the new number crosses a sensitivity point, reopen the design. The binding uses Spring Boot's relaxed names (max-import-rows to maxImportRows). Those are real binding rules, not a custom mapping language.

    Priorities in code, only as a mirror

    The tree remains a document because business importance is a human judgment. A small type can still mirror the leaves you have already prioritized, so a review bot can check that every High/High leaf has an owner and a confirmation note. It should not become a scoring product.

    java
    package com.techlearningpro.academies.drivers;
    public enum Rank { HIGH, MEDIUM, LOW }
    public record UtilityLeaf(
    String scenarioId,
    String attribute,
    Rank businessImportance,
    Rank technicalRisk,
    String owner) {
    public boolean designNow() {
    return businessImportance == Rank.HIGH
    && (technicalRisk == Rank.HIGH || technicalRisk == Rank.MEDIUM);
    }
    }

    designNow encodes the guild's current rule: High business importance with High or Medium technical risk enters the iteration. Medium/Low does not. The rule is a decision, and it is allowed to change in an ADR. Hiding the rule in a meeting makes the next initiative's tree incomparable.

    What the document refuses to decide

    Vendor of the database, shape of the logo API, the CSS variables for branding, the choice between two CSV libraries, and the internal package names. Refusing is a feature. A driver document that picks a CSV library has slid into design and will be maintained by nobody. A driver document that picks "the tenant id comes from the token" has done its job, because every later design that disagrees is wrong against G2 rather than merely different.

    Practical Implementation or Design Walkthrough

    Problem

    Produce the driver document for TechLearningPro Academies at the moment the first three enterprise contracts are likely and none has been signed. The output must let an ADD iteration start on isolation and IdP registration, and must stop branding and a per-tenant Kubernetes cluster from consuming that iteration.

    Goals, constraints, assumptions

    Goals G1–G4 are the ones in the core concepts section. The sponsor who can accept trade-offs is the general manager for Academies, named on the document, not "product."

    IdConstraintSourceIf violatedEscalation
    C1One squad operates productionStaffing planDesign is not shippableGeneral manager, before the design is drawn
    C2EU tenant data remains in an EU region when the contract says soDraft contract templateLegal exposureCounsel, before signing a region we cannot place
    C3Tenant users authenticate with OIDCCompany integration standardFirst customer cannot connectSecurity architecture, if a customer demands a different protocol
    C4No second learner password storeExisting platform IAMSplit identitiesIAM owner
    AssumptionOwnerConfirm by
    ≤ 200 tenants in year oneSponsorQuarterly plan
    Import files ≤ 10,000 rowsFirst-customer discoveryWorkshop before build
    Interactive search can live on the primary database at steady rates in the YAMLSquadA prototype against production-shaped data before the second contract

    ASRs that pass the tests

    • ASR-1 Isolation. Given a token for tenant A, any API, export, or default log line that reads roster or employee data returns only tenant A, and does not reveal whether a tenant B identifier exists.
    • ASR-2 IdP registration. An operator can attach one OIDC issuer to a tenant without a code change. Tokens from any other issuer are rejected for that tenant.
    • ASR-3 Import atomicity. A failed or partial import does not replace the roster readers see. Success replaces it in one commit. The admin request that uploads the file returns a job id, not the finished roster.
    • ASR-4 Staff default deny. A staff principal without a time-bounded break-glass grant cannot read employee email or name. Operational metrics may include tenant id and counts.

    Logo upload, color, and certificate layout are listed under non-drivers.

    Iteration selection

    UtilityLeaf values for the table earlier: isolation and IdP are designNow. Import is High/Medium, so it is designNow as well if the iteration can carry three, and lesson 2's rule was "a few." The guild's call for the first iteration is isolation plus IdP, because both are High technical risk and they share a trust boundary. Import is the next iteration, with the risk left open and dated. Branding is not designNow. Writing that down is the walkthrough's result. No container diagram is produced in this lesson on purpose. A container diagram would be a tactic prematurely frozen.

    Review harness

    java
    package com.techlearningpro.academies.drivers;
    import java.util.ArrayList;
    import java.util.List;
    public record DriverFinding(String code, String message) {}
    public final class DriverDocumentReview {
    public List<DriverFinding> review(
    List<UtilityLeaf> leaves,
    boolean constraintHasEscalation,
    boolean assumptionsHaveOwners,
    boolean nonDriversListed) {
    var findings = new ArrayList<DriverFinding>();
    boolean anyDesignNow = leaves.stream().anyMatch(UtilityLeaf::designNow);
    if (!anyDesignNow) {
    findings.add(new DriverFinding(
    "NO_ITERATION_DRIVER",
    "Mark at least one High/High or High/Medium leaf or the tree has not been prioritized"));
    }
    long highHigh = leaves.stream()
    .filter(l -> l.businessImportance() == Rank.HIGH && l.technicalRisk() == Rank.HIGH)
    .count();
    if (highHigh > 3) {
    findings.add(new DriverFinding(
    "TOO_MANY_HIGH",
    "More than three High/High leaves usually means the room refused to compare"));
    }
    if (!constraintHasEscalation) {
    findings.add(new DriverFinding(
    "CONSTRAINT_WITHOUT_ESCALATION",
    "Every constraint needs an escalation path"));
    }
    if (!assumptionsHaveOwners) {
    findings.add(new DriverFinding("UNOWNED_ASSUMPTION", "Every assumption needs an owner"));
    }
    if (!nonDriversListed) {
    findings.add(new DriverFinding(
    "MISSING_NON_DRIVERS",
    "List requirements that must not reshape the architecture"));
    }
    return List.copyOf(findings);
    }
    }

    The "more than three High/High" rule is a facilitation smell, not a law of systems. It catches the sticker sheet. A genuine system can have four hard scenarios; the reviewer then overrides in writing, with the sponsor's comparison still attached. The override is a decision. The default is skepticism.

    Failure handling at driver level

    The import ASR already states the failure response: previous roster remains, job shows failure. The isolation ASR states the failure response for a bad caller: no rows and no existence leak. Staff access states default deny. These are requirements about failure, and they are architectural because they forbid "return the exception message from the database driver" and "log the uploaded CSV on error." A later design that logs the file for debugging has violated ASR-4 and ASR-1 even if the request returned 400.

    Testing the drivers, before the system exists, means review against this document plus a characterization test plan: one test that a repository cannot be called without a tenant context, one test that a foreign id does not change the status code in a distinguishing way, one test that a failed import fixture leaves the prior roster. Those tests are the evaluation move for this lesson. They are not a full strategy for multi-tenant security. They are the minimum that makes the High/High leaves falsifiable.

    Enterprise Real-World Scenarios

    The logo that nearly became a service

    An early Academies workshop spent two hours on certificate branding because the first prospect's marketing team was in the room and the isolation scenario was phrased as "the app will be multi-tenant," which everyone agreed with. A proposal followed: a branding service, a per-tenant theme pipeline, and a separate deploy so "enterprises get flexibility." The branding service would have been the first process boundary in a product that did not yet have a tenant predicate.

    The driver document, written after the workshop, put branding in the non-driver list with a sentence the prospect's sponsor could accept: colors and a logo are stored as tenant-scoped assets in the existing media bucket, authorized like any other tenant object, and they do not get a service until a scenario says the asset pipeline threatens a High leaf. Isolation and IdP registration took the iteration. The prospect's marketing team still got a logo, in a story, inside the structure. The architectural save was the classification, not a compromise that drew the service "just for now." Temporary process boundaries are how G4 dies.

    The same document made the technical-risk score for isolation High even though row filters feel easy. The team had shipped a tenant_id column before, and a report job had joined without it. That history is why the technical risk is High. A tree written from optimism would have scored it Low and designed branding infrastructure instead. Utility trees are only as good as the willingness to remember the last miss.

    Residency is not a database per tenant

    A later contract draft said employee data for a German customer had to remain in the EU. An engineer translated that sentence into "database per tenant" and estimated a fleet. The constraint's source was the draft contract. The violation consequence was legal, not operational. The escalation path was counsel and the sponsor, not a design spike on automation.

    Counsel's reading, recorded as an assumption with counsel as owner, was that region placement of the store satisfied the draft, and that a dedicated database was not required. The trade-off point stayed "shared schema inside a region." A second region for EU data became a possible future constraint, gated on the first EU signature, and it was not allowed to rewrite the tenant-predicate sensitivity point. Had the contract truly required a private database, G4 would have been escalated before the squad promised to operate a fleet. The document's value was the pause between a sentence and a fleet.

    Both stories are TechLearningPro initiatives. Both turned on classification. The feature was never the driver. The goal, the constraint source, and the High/High scenario were.

    Best Practices

    • Start from goals that can reject a design. If a goal cannot, rewrite it or drop it.
    • Run the three ASR tests before a functional story is allowed to create a component.
    • Keep constraints out of the priority column. Give each an escalation path.
    • Label assumptions with owners and confirmation dates. Promote or kill them on that date.
    • Build the utility tree with the sponsor in the room for business importance, and with the people who will be on call for technical risk.
    • Force a comparison when every leaf is High. Three High/High leaves is already a full iteration.
    • Write the non-drivers down. Omissions that are explicit do not come back as "you forgot the logo."
    • Describe steady, peak, and batch separately whenever a job and a user share a store.
    • Name sensitivity points as properties you could get wrong in one line of code.
    • Name trade-off points as properties you are choosing, not as problems you have solved.
    • Mirror only the settled priorities in code. Do not build a scoring service to avoid a conversation.
    • When a contract sentence appears, quote it into the constraint section before translating it into technology.

    Common Mistakes and Anti-Patterns

    The flat backlog as the architecture input. Every story is estimated, none is marked significant, and the iteration is whatever was groomed to the top. Isolation loses to a logo because the logo has a screenshot. The repair is the ASR tests and a tree, done before grooming spends the month.

    Preferences filed as constraints. "Must be event-driven," "must be microservices," "must use the database I know." They have no source beyond taste, and they shut down G1-compatible designs. The repair is the source column. Empty source means preference, and preferences go in an appendix that cannot block a High/High scenario.

    Constraints filed as stories. Data residency is a ticket in sprint 12, slipped twice, and the schema is already live in one region with a tenant from the contract. The repair is to treat the constraint as a gate on design and on signing, with an escalation path. A story can implement the gate. A story cannot vote the gate off the island.

    Unowned numbers. "Ten thousand rows" lives in a slide, the code uses 500, and the first customer sends 10,000. Or the code uses 10,000 as a hard constant and nobody knows who to ask when the second customer has 80,000. The repair is an assumption with an owner and a configuration value that matches the document. Changing it is an event, not a quiet edit.

    The all-High tree. Stickers instead of comparisons. ADD then either stalls or smuggles in every tactic. The repair is the comparative question and a facilitation smell when High/High grows past what one iteration can address. Override only in writing.

    Solving the trade-off by drawing both sides. A shared database and a database-per-tenant "for later" and a schema-per-tenant option, all in the first diagram, because the document mentioned the trade-off. Mentioning it was so the chosen side would be tested. Drawing every side is refusing the decision. The repair is one choice, the sensitivity point of that choice, and the evidence that would reopen it.

    Client-supplied tenant identity. A tenantId field accepted from JSON because the UI "already knows." This fails the sensitivity point even when tests pass for honest clients. The repair is structural: repositories take a context built from the token, and the body's copy is ignored or rejected when it disagrees.

    Troubleshooting and Design Review

    Symptom: The design review reopens "what are we building" every week.

    Likely cause: goals are slogans, or non-drivers were never listed, so every stakeholder believes their story is the architecture.

    Investigation: read sections 2 and 11. Ask which goal a disputed component serves.

    Corrective action: rewrite the goal until it can reject something, and park the rejected something under non-drivers with the sponsor's name.

    Symptom: A High/High scenario has no test and no owner three weeks later.

    Likely cause: the tree was a workshop artifact and not an input to ADD. Technical risk was treated as a feeling.

    Investigation: find the iteration goal. If the scenario is absent, the tree lost.

    Corrective action: stop the next feature iteration and take the leaf as the goal. Assign the sensitivity point to a named developer and a failing test.

    Symptom: Import jobs periodically freeze admin search.

    Likely cause: batch and interactive profiles were collapsed. The job holds a lock or uses the interactive pool.

    Investigation: compare the running import to the workload section. Look for a request thread doing the parse.

    Corrective action: make the job the artifact the ASR described. Bound its lock to the final commit, not the parse. If the database cannot do that, the assumption about "one primary is enough" is false and the risk must be escalated, not patched with a longer HTTP timeout.

    Symptom: A tenant id from the request body reaches a query.

    Likely cause: the sensitivity point never became a type the compiler or the review enforces. It stayed a sentence.

    Investigation: search for request parameters named like the tenant. Trace one repository method.

    Corrective action: a context object built at the edge from the token, required by the repository, and a test that a mismatched body id does not change the query. This is the moment a driver becomes structure. Lesson 4's dependency inversion is one way to make the repository hard to call wrong. The driver, not the principle, is the reason.

    Symptom: EU residency surprises the squad at contract signature.

    Likely cause: the constraint was paraphrased into technology ("we might need sharding") and the original sentence was lost. Counsel was not the escalation path.

    Investigation: find the contract sentence and C2.

    Corrective action: restore the sentence, get the reading from counsel, and only then choose region placement or a harder isolation tactic. Do not commission a fleet in the gap.

    Review questions: Can each goal reject a design? Does each constraint have a source and an escalation path? Which requirements failed the ASR tests, and are they listed as non-drivers? Which leaves are High/High, and who scored technical risk? What are the three workload profiles? What is the sensitivity point of the chosen trade-off? Which assumption, if false, invalidates the iteration?

    Interview Questions and Answers

    Q1. What is an architecture driver, and what inputs are deliberately not drivers?

    A driver is a business goal, a requirement, or a constraint that matters to success and is likely to shape a structure or a binding limit on structures. Functional stories that fit inside an existing structure, preferences without a source, and unconfirmed numbers are not drivers. Unconfirmed numbers are assumptions with owners. I want a short driver document, not a backlog reprinted under a new title. If a candidate calls every story a driver, they cannot choose an ADD iteration.

    Q2. How do you decide a functional requirement is architecturally significant?

    I apply three tests. Does meeting it force a structure, a relation, a data owner, or a trust boundary? Does it create a sensitivity point where a small mistake flips a high-priority scenario? Is the harm of failure disproportionate to the size of the story? Roster isolation passes all three. A certificate logo usually fails the first and third, so it is scheduled as ordinary work. Effort in person-days is not the test. A tedious report inside one module is not an ASR. A one-line tenant predicate is.

    Q3. What is the difference between a constraint and a requirement?

    A requirement can be ranked, deferred, or renegotiated with the stakeholder who asked. A constraint is already fixed or imposed from outside, has a source, and is obeyed or explicitly escalated. "Search p95 under 300 ms" is a requirement. "EU tenant data stays in an EU region per the contract" is a constraint. Misfiling a constraint as a story lets a sprint drop it. Misfiling a preference as a constraint freezes a design for no authority. I ask for the source and the escalation path.

    Q4. How does a utility tree get used in the next design iteration?

    The tree hangs concrete scenarios under quality refinements and scores each leaf twice: business importance and technical risk. The next iteration takes a few High/High and High/Medium leaves, not the whole tree. I facilitate business scores with the sponsor using comparisons, and I own the technical-risk score, including the honesty to mark something easy. Branding at Low/Low stays off the diagram. Isolation at High/High becomes the ADD goal. A tree where everything is High has not been prioritized.

    Q5. What is a sensitivity point? Give an example from multi-tenant data.

    A sensitivity point is a property that a particular quality response depends on. For shared-schema multi-tenancy, the property is that every read, export, and default log is scoped by a tenant id taken from the authenticated principal, not from the client body. Get that property wrong and the isolation scenario fails even if the rest of the diagram says "multi-tenant." I write the property in the driver document so a later refactor that "simplifies" the repository signature has to reopen G2.

    Q6. What is a trade-off point, and how do you record one?

    A trade-off point is a property that helps one quality and hurts another. A shared database helps operability and a single codebase, and it concentrates isolation risk into the tenant predicate. I record the choice, the qualities on each side, the stakeholder who accepted the hurt side, and the evidence that would reopen it, such as a residency clause counsel says a shared regional database cannot meet. I do not draw all alternatives as if drawing them resolved the trade-off.

    Q7. Why do workload profiles belong in a requirements document?

    Because quality scenarios have an environment slot, and "normal load" is not an environment. Steady interactive traffic, a per-tenant peak, and a batch import stress different resources and forbid different designs. An import on the request thread can meet a functional story and fail the interactive latency scenario whenever an import is running. I state units and owners. I do not pretend the profile is a capacity model. Lesson 24 is where the arithmetic gets serious. The profile is already enough to reject illegal shapes.

    Q8. A stakeholder says every scenario is critical. What do you do in the room?

    I ask them to compare two concrete failures: a cross-tenant roster leak, and a wrong logo on a certificate, and to say which one they will explain to the customer if only one can shape this month's design. Comparative questions produce ranks. I also cap the practical High/High set for one iteration and treat a long High list as a facilitation smell, overridable in writing. My job is not to tell the sponsor their branding does not matter. It is to keep branding from consuming the iteration that isolation requires.

    Q9. How do you keep a driver document from turning into a design?

    I state properties and illegal options, and I stop before products and package names. "Tenant id comes from the token and is required by every repository" is a driver. "Use library X to parse JWT inside the entity" is a design, and usually a bad one. "Import is a job with an atomic replace, and it must not hold the interactive lock during parse" is a driver. "Use this queue vendor" belongs to a later lesson once a scenario demands a mechanism the database cannot provide. The document lists non-drivers so the boundary stays visible.

    Q10. What risks belong in an architecture driver document?

    Risks that name a scenario and a missing or fragile fact: a query path that might omit the tenant predicate, an assumption about import size that discovery has not confirmed, a contract sentence that might mean more than region placement. Each risk has a mitigation that is a structure or a test, or an escalation, and an owner. Project risks such as "vendor might be late" can live elsewhere. "We will be careful" is not a mitigation I will accept in review.

    Q11. How would you represent still-unconfirmed numbers without letting them look like measurements?

    As assumptions with owners and confirmation dates, mirrored in configuration such as a Spring Boot @ConfigurationProperties record the environment can override. The YAML is not evidence. It is the guess made visible so a change to the guess is a change to the driver set. When confirmation fails, I reopen the scenarios whose environment used the guess. I do not bury the number in a constant with no owner.

    Q12. How do business goals constrain a multi-tenant proposal that starts with a repository fork per customer?

    I test the proposal against the goals. A fork can satisfy a misplaced reading of isolation and will fail a goal of selling to many customers without a custom codebase, and it will fail a staffing constraint of one squad. I then show the trade-off we did accept: one codebase, tenant predicate, sensitivity point under test. If a customer's contract truly requires a private deployment, that is an escalation against the goal, decided by the sponsor, not a quiet fork in the first design. The interview answer I want is the goal test, not a lecture on multi-tenancy patterns.

    Hands-On Exercises

    Exercise: Architecture driver document for a multi-tenant academy

    Problem statement. Write the driver document for a multi-tenant learning product sold to employers. One squad will operate it. At least one prospect needs employee data to remain in a stated region. Administrators import rosters and must not see other tenants. Use your own goals if you can defend them. Do not copy the Academies YAML numbers without labeling them as your assumptions.

    Tasks.

    1. Write four business goals that can each reject a design, and name the sponsor role who accepts trade-offs.
    2. List constraints with source, violation consequence, and escalation. Include one preference you refuse to promote.
    3. List assumptions with owners and confirmation plans.
    4. Mark three functional requirements as ASRs or as non-drivers, and show the test you used.
    5. Build a utility tree with at least six leaves and both priorities. Select an iteration of at most three leaves. Leave the others visible.
    6. Define steady, peak, and batch profiles with units.
    7. Name one sensitivity point and one trade-off point for the tenancy approach you chose.
    8. State the failure response for a bad import and for a cross-tenant read.

    Expected deliverables. The document (about three pages), the tree, and a paragraph on what your first ADD iteration will not decide.

    Evaluation rubric.

    Assessment areaWeightWhat "good" looks like
    Classification20%Goals, constraints, assumptions, ASRs, and non-drivers are distinct
    Utility tree25%Compared priorities; iteration is smaller than the tree
    Workloads15%Three profiles, and they forbid at least one naive design
    Trade-off honesty25%Sensitivity point is testable; the hurt quality has a named acceptor
    Risks and escalation15%Open risks tie to scenario ids; constraints can be escalated

    A document that includes a queue vendor, a service mesh, and a branding microservice has skipped the exercise and started a later lesson.

    Lesson Summary

    Architecture drivers are the few inputs that may shape a structure: goals that can reject a design, requirements that pass the significance tests, quality scenarios with priorities, and constraints with sources. Everything else is backlog, preference, or an assumption waiting for an owner.

    The utility tree is how a room stops treating those inputs as equal. High business importance and high technical risk form the next ADD iteration. Constraints do not compete in that ranking. Workload profiles make environment slots real enough to forbid a design that mixes a long import with an interactive lock. Sensitivity points and trade-off points are the sentences a later review is not allowed to tidy away.

    Lesson 4 drops from this altitude into the module that isolation and tax rules will be written in. SOLID is applied there as a way to protect a driver, and it is withheld when no driver has asked for the flexibility.

    Further Reading

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