What does it mean that architecture is "continuous" rather than an up-front phase, and how do you keep a system's real architecture from drifting away from the intended one?
answer
- Least knowledge at the start — BDUF locks in worst-informed decisions
- Last responsible moment: defer + name the trigger
- Drift (unintended) vs erosion (violation)
- Fitness function = objective, automated characteristic check
- Cheap deployment turns one-way doors into two-way doors
basics
~20 sRequirements and technology change, so architecture decisions get made throughout a system's life, not once at the start. To stop reality drifting from intent, you enforce the important rules automatically in the build and pipeline instead of only writing them down.
solid answer
~60 sBig Design Up Front fails because the moment of least knowledge about a system is its start — you'd be locking in decisions exactly when you're least qualified to make them. Continuous architecting means treating architecture as a stream of decisions across the system's life: decide only what must be decided now (the **last responsible moment** — the point after which delaying costs more than deciding), keep options open where it's cheap, and revisit decisions as forces change. That creates a governance problem: intent recorded in documents diverges from what's built (**drift** — unintended structures creep in; **erosion** — the code actively violates the intended architecture). The answer from *Building Evolutionary Architectures* (Ford, Parsons, Kua) is **fitness functions**: objective, automated tests of an architectural characteristic, run in CI. Module dependency rules enforced by a build-failing test, p99 latency budgets asserted in the pipeline, licence/vulnerability gates, a check that no service reaches another's database. Combined with ADRs (why) and cheap deployment (so revisiting is affordable), this makes architecture something you continuously *verify*, not something you continuously *hope for*.
code
pseudocode · 18 lines// A fitness function is just an automated, objective assertion about an
// architectural characteristic, run in CI like any other test.
test "no module may depend on another module's internals" {
let graph = analyzeImports(sourceRoot)
let violations = graph.edges.filter { e ->
e.to.isInternalOf(otherModule) && e.from.module != e.to.module
}
assert violations.isEmpty(), "boundary violated: ${violations}"
}
test "checkout p99 latency stays under budget" {
let result = loadTest(endpoint: "/checkout", rps: 500, duration: 2.min)
assert result.p99 < 300.ms, "p99 was ${result.p99}, budget 300ms"
}
// Failing build == the architecture decision is being violated NOW,
// not discovered at a quarterly review six months later.go deeper
Explain that requirements and technology keep changing, so architectural decisions keep being made; mention that automated checks in the build can stop the code from drifting away from the intended design.
Contrast BDUF with continuous architecting, explain the last responsible moment with a trigger condition, define fitness functions with two concrete examples (a dependency rule and a latency budget).
Name drift vs erosion, classify fitness functions (atomic/holistic, triggered/continual), and connect continuous architecting to delivery capability — cheap deployment is what makes revisiting decisions affordable. Mention strangler fig for reversing 'irreversible' choices.
Frame it as governance at scale: how do you make the compliant path the easy path (golden paths, platform enforcement) rather than policing teams? How do you decide which characteristics deserve a fitness function given their maintenance cost? How do you handle domains where deployment isn't cheap (regulated, embedded) and the balance shifts back toward up-front analysis? Discuss measuring architectural health as a portfolio concern.
## Why up-front-only architecture fails The classic waterfall model put architecture in a phase: analyse, design, build, test. Three things break it. 1. **The knowledge paradox.** You know least about a system at its beginning — least about the real load profile, the real failure modes, the requirements the customer hasn't articulated yet. Yet BDUF asks you to lock the most expensive decisions precisely then. 2. **Requirements change.** Not because stakeholders are undisciplined, but because building the system teaches everyone what they actually needed. 3. **The technical ecosystem changes.** A five-year-old system is running on a platform, in a security climate, and against a cost model that didn't exist when it was designed. *Building Evolutionary Architectures* calls this **dynamic equilibrium** — the ecosystem itself shifts under you, so a design that was optimal is now merely legacy. The conclusion is not "do no design up front". You must make the genuinely irreversible decisions early because you have no choice (see one-way doors). The conclusion is: **decide as few things as possible up front, and only the ones that can't be deferred.** ## Last Responsible Moment From Lean software development (Mary and Tom Poppendieck): defer a decision until the **last responsible moment** — the moment after which *not* deciding costs more than deciding. Two failure modes bracket it: - **Too early**: you commit with the least information, and you pay for optionality you never used (speculative generality — the abstraction layer for the second database you never added). - **Too late**: work stalls, or the decision is made implicitly by whoever committed code first, and you inherit an accidental architecture. Deferring is only responsible if you *actively* keep the option open — an interface, a seam, a feature flag — and if you name the trigger that forces the decision ("when we onboard tenant #2", "when write volume passes 5k/s"). "We'll decide later" without a trigger is procrastination wearing a principle's hat. ## Drift and erosion: two distinct decay modes - **Architecture drift** — the implementation accumulates structures the intended architecture never contemplated. Nobody violated a rule; the rule simply didn't cover the case. Symptom: the diagram is no longer a description of anything. - **Architecture erosion** (also *architectural decay*, *violation*) — the implementation actively breaks the intended constraints: a service reads another service's tables directly; a UI component calls the database; a layer is bypassed for speed. Both compound silently, because nothing fails when they happen. The gap between intended and actual architecture is sometimes called the **architecture/implementation gap** or, colloquially, the difference between the architecture you *documented* and the architecture you *deployed*. ## Fitness functions — the core mechanism Definition (Ford/Parsons/Kua): *an architectural fitness function provides an objective integrity assessment of some architectural characteristic.* The name borrows from evolutionary computing, where a fitness function scores how close a candidate is to the goal. The critical properties are **objective** (a number or a pass/fail, not an opinion) and **automated where possible** (so it runs on every change, not at review time). Examples across the quality-attribute space: | Characteristic | Fitness function | |---|---| | Modularity / boundaries | A build-failing test asserting no cycles between modules and that module A never imports module B's internals (e.g. ArchUnit-style dependency tests, dependency-cruiser rules) | | Layering | Test that the persistence layer is never referenced from the presentation layer | | Performance | CI stage asserting p99 latency of a key endpoint stays under N ms against a fixed load profile | | Availability / resilience | Chaos experiments: kill an instance in staging and assert the SLO holds | | Security | Automated dependency-vulnerability scan; a test asserting every endpoint is covered by an authorization rule; secret scanning | | Cost | A pipeline check that projected monthly infrastructure cost hasn't risen more than X% | | Data integrity | Contract tests between producer and consumer of an event schema | | Elasticity | Load test asserting scale-out occurs within N seconds of a traffic step | Categories worth knowing: **atomic** (checks one element in isolation, e.g. a dependency rule) vs **holistic** (checks emergent behaviour of several elements interacting, e.g. end-to-end latency under load with the cache disabled); **triggered** (runs in CI/on deploy) vs **continual** (runs constantly in production, e.g. synthetic monitors, chaos engineering); **static** (fixed threshold) vs **dynamic** (threshold varies with context, e.g. allowed latency scales with current user count). The practical rule: **for every architectural characteristic you claim to care about, name its fitness function.** If you can't, either you don't actually care about it or you have no idea whether you have it. ## The other enablers of continuous architecting - **Cheap, safe deployment.** Continuous delivery, automated tests, trunk-based development. Revisiting a decision is only realistic if changing the system is routine. This is why delivery-capability investment is architectural work: it converts one-way doors into two-way doors. - **Decision records with expiry conditions.** ADRs capture the forces; a decision that names the conditions under which it should be revisited turns "continuous architecting" into a scheduled activity rather than an aspiration. - **Deliberate seams.** Anti-corruption layers around third-party systems, ports-and-adapters boundaries, versioned contracts. These are where you *buy* reversibility — and they cost complexity, so buy them where the probability of change is real, not everywhere. - **Incremental change patterns.** The **strangler fig** pattern (Fowler): route traffic through a facade, gradually replace behaviour behind it, retire the old system when nothing routes to it — the standard way to change a decision that was supposed to be irreversible. **Branch by abstraction**: introduce an abstraction over the thing you want to replace, implement the new side behind it, flip, remove. - **Reviews that look at trends.** Change-coupling analysis (which files always change together — a coupling smell), hotspot analysis, dependency graphs over time. These surface drift that no single code review would catch. ## Edge cases and honest limits - **Fitness functions can enforce the wrong thing.** A stale dependency rule that no longer reflects intent becomes a tax teams route around with suppressions. Review them like code; delete the ones that stopped mattering. - **Not everything is automatable.** Some characteristics (developer experience, domain-model fit, appropriateness of an abstraction) need manual review; make those explicitly *manual* fitness functions with a stated cadence rather than pretending they don't exist. - **Continuous ≠ constant churn.** The point is that decisions *can* be revisited when forces change, not that they *should* be relitigated every sprint. Stability of interfaces is itself a valuable property. - **Some domains genuinely need more up-front work** — regulated, safety-critical, or hardware-coupled systems where deployment is not cheap and mistakes are not recoverable. The principle survives; the balance point moves.
- How is a fitness function different from a normal unit test?Scope and subject. A unit test asserts that a piece of code produces the right functional output. A fitness function asserts that an *architectural characteristic* still holds — a structural rule (no cycles between modules), a quality-attribute budget (p99 latency, error budget), a security posture (every endpoint authorized), a cost ceiling. Many are implemented with the same test framework, and that's fine; the distinction is what they protect. Some fitness functions aren't tests at all — chaos experiments and production synthetic monitors run continually.
- Doesn't 'defer the decision' just mean nobody decides and you end up with an accidental architecture?That's the failure mode, yes. Deferral is only responsible when two things are true: you actively preserve the option (an interface or seam that makes the later choice cheap) and you name the trigger that forces the decision — a traffic threshold, a second customer, a compliance date. Deferral without a trigger and without a preserved seam is not the last responsible moment; it's the first irresponsible one.
- How do you change a decision that turned out to be genuinely hard to reverse?Incrementally, behind a facade. The strangler fig pattern puts a routing layer in front of the old system, moves capabilities behind it one at a time, and retires the old system when nothing routes there — so you never do a big-bang cutover. Branch by abstraction does the same inside a codebase: introduce an abstraction over the component, build the replacement behind it, switch traffic with a flag, then delete the old implementation. Both convert an irreversible decision into a long sequence of reversible ones.
- Doesn't continuous architecting conflict with stable interfaces that other teams depend on?No — continuous means decisions *may* be revisited when forces change, not that they should churn. Interface stability is itself a quality attribute you can protect with a fitness function (contract tests, backward-compatibility checks on schemas). What continuous architecting rejects is the idea that decisions are frozen because a document was signed, not the idea that some things should be deliberately stable.
A garden, not a statue. You don't carve it once and walk away; you plant with intent, then keep pruning as things grow in directions you didn't plan. Fitness functions are the gardener's stakes and trellises — they don't do the growing, but they make it immediately visible and physically awkward when a branch heads somewhere it shouldn't.
saying these in an interview costs you the question
- Reading 'continuous architecting' as 'no design up front at all' — genuinely irreversible decisions still have to be made early and deliberately.
- Treating architecture documents as governance: writing the rule down does nothing to prevent erosion.
- 'We'll decide later' with no preserved seam and no trigger condition — deferral becomes an accidental decision by whoever commits first.
- Confusing drift (unintended structures appear) with erosion (intended constraints are actively violated) — they need different responses.
- Adding abstraction layers everywhere 'in case we change our mind'; speculative generality has real cost and is usually unused.
- Claiming to care about a quality attribute with no objective measurement of it anywhere in the pipeline.
- Letting fitness functions become stale and unquestioned, so teams route around them with suppressions.