Software Design Reference System Architect · Understand the Problem and the Domain

Lesson 0051 · System Architect · Module 1

Model the Problem Before the System

Architecture is downstream of a business question: what actually matters here, and where does the valuable complexity live? Classify the domain before you draw a box.

Reading ≈ 11 minPractice ≈ 30 min

Mission tie-in: the discipline of the senior designer starts before code — knowing which part of the business deserves bespoke modeling and which deserves a vendor. This lesson gives you the map.

Knowledge: understand the problem, then invest

Before asking what to build, assemble the problem from four ingredients. Goals — what must improve, stated as outcomes (lesson 0039). Stakeholders — who cares, whose judgment matters. Requirements — what the system must do, expressed as behavior. Constraints — what binds the solution regardless of preference: a regulated market, a fixed deadline, a legacy order platform, an existing payment rail.

Constraints deserve special attention because they rule out options. The returns system cannot replace the order platform, so the design must consume its data as-is. Constraints are not obstacles to design around; they are the first evidence the design is built on.

Requirements become concrete as use cases: for each one, an actor, a goal, and the sequence the system must support (Larman, ch. 6). The business scenarios you will gather in lesson 0052 are their raw material.

Once the problem is assembled, one lens is particularly useful for deciding where the effort goes: classify the business into subdomains — areas identified by the work they perform, not by teams or tables:

Returns business · subdomains by type
SubdomainTypeWhy
Eligibility policyCoreDecides which returns are accepted — the company's differentiator and its legal risk.
Return journeySupportingNeeded for the business to work, but not what makes it special.
Label printingGenericCarriers sell this off the shelf; no competitive edge in writing it.
Payments settlementGenericRefunds are a solved problem; vendors and payment rails do it better.

The classification is not an opinion poll, and it is not a build-versus-buy decision by itself. Core subdomains are where the business differentiates itself — often worth the most modeling attention and hardest thinking. A business may have more than one. Supporting subdomains are necessary but do not create the differentiation. Generic subdomains have widely available solutions. For supporting and generic work, prefer commodity treatment, but still compare vendor fit, regulation, integration risk, total cost, and strategic control before choosing to buy or build (lesson 0027).

The principle behind the principle

Classification is not taxonomy for its own sake. It is a cheap proxy for one quantity: how much business value moves when the model gets better. In a generic subdomain the market has already driven that quantity to near zero — a more thoughtful label-printing model prints the same label. In a core subdomain the model is the product: a slightly better eligibility model accepts different returns, and that changes fraud loss, contact rate, and how many cases land on a human. Design effort should follow that derivative, and three labels are the cheapest way to estimate it before you have any measurements at all.

Seeing the mechanism also tells you where the lens stops working. It assumes a functioning vendor market, a durable advantage, and decisions you actually own. If no vendor can meet a binding constraint, the generic label does not make the work go away. If every competitor automates return adjudication within two years, today's core decays into tomorrow's supporting work. And if the business has already bought the whole capability, your design question is the anti-corruption layer around someone else's model, not how to split your own investment. Where an assumption fails, keep the labels but re-derive them — and write down the fact that would change them.

This is where you also find the domain experts. Every subdomain has people who carry its rules; core subdomains often depend on a few people whose judgment shapes the business. Identify them early — they are the people you will learn from in lesson 0052.

Then ask the question the whole sequence depends on: where does the valuable complexity live? A returns system whose policy is "always accept, no questions" has almost no domain to model — the complexity is in logistics integration. A company whose edge is adjudicating edge-case eligibility has complexity in the policy model. Complexity location decides where your architecture, data, and testing attention go. Spending equally everywhere is the beginner's move; spending on the core domain is the professional's.

One distinction keeps the lens honest: a business capability is what the business can do (returns management), while a subdomain is the problem space that capability serves. Conflating them produces a list of features rather than a map of complexity — useful, but not the same thing.

# A cheap way to record the classification before anyone draws a service.
# The point is the labels, not the dataclass: they drive where effort goes.
from dataclasses import dataclass
from enum import Enum


class Kind(Enum):
    CORE = "core"
    SUPPORTING = "supporting"
    GENERIC = "generic"


@dataclass(frozen=True)
class Subdomain:
    name: str
    kind: Kind
    why: str
    expert: str | None = None


RETURNS = {
    "eligibility_policy": Subdomain("Eligibility policy", Kind.CORE,
        "Adjudicating edge cases is the company's edge.", "Anya, policy owner"),
    "return_journey": Subdomain("Return journey", Kind.SUPPORTING,
        "Needed to run the business; not a differentiator."),
    "label_printing": Subdomain("Label printing", Kind.GENERIC,
        "Buy from the carrier; do not rebuild."),
    "payments": Subdomain("Payments settlement", Kind.GENERIC,
        "Use the existing payment rail."),
}

Worked: proving that eligibility is the core

An architect handed the returns brief does not open with a list of subdomains. They open with the numbers in the initiative frame (lesson 0039) and ask one question of each: which of these moves if the model gets better?

The frame gives four. Median resolution 72 hours, to be brought under 4. A 41% support-contact rate, to be cut by a quarter. Fraud loss no worse than the current baseline. A manual review queue of at most 50 cases a day. The pilot cohort runs roughly 3,000 return requests a day; the 40 requests/second peak in the driver set is portal traffic — status checks and page loads — not decisions. Three of those four numbers turn out to be downstream of one thing: how many requests the system can decide without a human, and how confidently it can defend each decision afterwards.

Then arithmetic assigns the label for you.

# The arithmetic that turned a label into a decision.
DAILY_REQUESTS = 3_000        # pilot cohort: eligible domestic returns per day
MANUAL_QUEUE_CAP = 50         # guardrail from the initiative frame (lesson 0039)


def manual_queue(auto_decision_rate: float) -> float:
    """Requests a day that fall out to a human reviewer."""
    return DAILY_REQUESTS * (1 - auto_decision_rate)


def within_guardrail(auto_decision_rate: float) -> bool:
    return manual_queue(auto_decision_rate) <= MANUAL_QUEUE_CAP


assert round(manual_queue(0.92)) == 240    # a competent generic rules engine
assert not within_guardrail(0.92)          # five times the guardrail
assert within_guardrail(0.984)             # 48/day — what the core model must reach

A configurable rules engine plausibly reaches 92%: purchase window, receipt present, value under a threshold. That leaves 240 cases a day for humans — five times a guardrail that is not a preference but the support team's real capacity. Reaching 48 means modeling what a generic rule language cannot express: partial receipts, a VIP override carrying a named authority, evidence that arrives after a refusal, and the appeal path. Those are exactly the cases where the company's judgment differs from its competitors', and exactly where its legal exposure sits. That is what core means here — not that eligibility is the biggest or the hardest part, but that the last six percentage points of automation are worth more than everything else on the board.

The alternative the team rejected was not "build something worse". It was: buy the rules engine and staff the residual queue with two more agents. They rejected it on two grounds, and only one is technical. First, the queue grows with volume, so its cost is permanent and linear, while the model's cost is mostly one-off. Second, policy changes weekly, and lesson 0043's modifiability driver says one engineer must ship a new rule in a day. Routed through a vendor's rule console, each change becomes a ticket, a release window, and an operator who does not know why the rule exists. Eighteen months later that is the difference between a policy change landing on Tuesday and one landing next quarter.

Notice what the same arithmetic does to label printing. A better label model saves nothing — the carrier prints the same label — and the driver that does matter (99% of labels created within 15 minutes of carrier recovery) is about persistence and retry, not modeling. Generic, and the design attention it earns is one adapter, one idempotency key, and a pending state the customer can see.

Field notes · classification tells you where to spend
TypeModeling effortTechnology stanceInvestment default
CoreDeep, expert-driven where neededChoose what protects the advantageUsually highest
SupportingEnough to preserve its rulesPrefer simple, conventional choicesProportionate
GenericEnough to integrate safelyEvaluate available solutions firstMinimize bespoke effort
What people get wrong after reading this
“Core means the hardest or biggest part.”
Core means the highest business value per unit of model quality. The carrier adapter may be more code, more error handling, and more on-call pain than the policy engine and still be generic — because doing it better buys nothing a competitor cannot also buy.
“Core means build, generic means buy.”
The classification sets a default investment, not a procurement decision. A generic subdomain with no vendor that satisfies a binding privacy or regulatory constraint still gets built — as cheaply as the constraint allows. Run the comparison in lesson 0027 anyway.
“Classify once, at kickoff.”
Advantage decays. Once every competitor auto-adjudicates returns, eligibility slides toward supporting and the investment should follow. Give each core label a revisit trigger — a competitor's launch, a regulation, a vendor that finally fits — the same way an ADR carries one.
“A business has one core domain.”
It may have several, and the system you are building may contain none of them. A checkout that is pure integration around three vendors is a coordination problem: the design work is in contracts and failure handling, not in domain modeling.

When this is the wrong move. Skip the classification when the system owns no business decisions — an internal report exporter, a data pipeline that moves someone else's facts, a two-week experiment you plan to delete. Skip it when the capability has already been bought whole and your job is to integrate it. And do not perform it from an armchair: labels invented without a single conversation with the people who carry the rules are guesses with a nice table around them. If you cannot name the expert for a subdomain, you are not ready to classify it — go get scenarios first (lesson 0052).

Skill: sort the domain before the structure

A subdomain is identified by…

A constraint shapes design when it…

Label printing is generic, but no vendor meets a binding privacy constraint. The team should…

Practice: map your own problem space

Use a real business domain you know, or use the returns case described here and in lesson 0041. Write its goal, stakeholders, three requirements as behaviors, and binding constraints. List its subdomains from the work performed, classify each with one sentence of evidence, and name the experts you would consult. Finish by locating the valuable complexity and naming one fact that could cause you to reclassify a subdomain later.

In the lab and the capstone: the companion lab's phase-5 checkpoint works on a deliberately flawed returns-resolution service. Before changing any of its code, classify the parts it contains and ask where its design effort actually went — the mismatch between the classification and the care in the code is the finding. Then compare your map against the worked capstone's workflow and drivers artifacts, which carry this same reasoning through to a decision.

Reveal: the misinvestment signal

If your team's deepest design effort is in supporting or generic work, ask what evidence justifies it. Regulation, integration risk, or a poor vendor market may provide a good answer. If no such force exists, keep the part replaceable so a simpler implementation or vendor remains available without a rewrite.

Teach it back
  1. Explain to a senior engineer, in two sentences, why core is an investment decision rather than a description of difficulty.
  2. Take one system you own. Classify its three largest components this week, and name the one where your team's effort and the classification disagree — then say what you would stop doing.
  3. Predict the damage: if a team spends its deepest modeling effort on a generic subdomain for a year, name the specific change that becomes expensive, and who pays for it.

Your win

You can classify a business into subdomains, say which one deserves bespoke modeling and which deserves a vendor, and point at where the valuable complexity actually lives.

Read and watch deeper

Ask your agent-teacher to classify a description of your business into subdomains and then defend each label against a hostile counter-classification — the argument is where the thinking happens.