Skip to main content

Foundationbeginner

Architecture vs. Implementation

Overview

Every system has two architectures: the intended one, which lives in the diagrams and in people's heads, and the actual one, which is the dependency graph the code really has.

When the two diverge, the actual one wins. It is the one that determines the cost of change, the one that propagates failures, and the one the next developer will copy as a reference.

The Problem

The pattern is familiar. The documentation describes three layers with separated responsibilities and dependencies pointing inward. The code has a controller importing the HTTP client of an external service, a domain entity carrying serialization annotations, and a reporting module reading directly from the tables of four other modules.

Nobody decided that. Each individual step was reasonable under schedule pressure, and nothing prevented it. The intended architecture was never implemented — only drawn.

The common diagnostic error is to call this indiscipline. It is not. It is the predictable consequence of a boundary that exists as a verbal agreement rather than as a verifiable constraint. Every boundary that depends solely on memory will be crossed. It is a matter of time and turnover.

Core Concepts

Architectural drift

The gradual divergence between the intended and the actual architecture.

Drift rarely happens through one big decision. It happens by accumulation: a shortcut here under deadline, an import there that is "only temporary", a one-off exception that becomes precedent. Each step is small; the sum is structural.

The characteristic signal is the remark "we don't really follow the architecture here any more". When someone can say that, the drift is already known and tolerated — which is the stage before it becomes invisible.

Effective boundary versus nominal boundary

A nominal boundary lives in the diagram, in the documentation, or in a directory convention. It constrains whoever remembers it and chooses to respect it.

An effective boundary is enforced by something that fails when it is violated. The code does not compile, the test breaks, CI refuses the merge.

The distinction is binary in practice: a nominal boundary is a suggestion with the appearance of a rule.

The mechanisms that make a boundary effective

Ordered by strength — the higher, the less it depends on human vigilance:

MechanismStrengthCost
Separate process or repositoryVery high — the import goes; the declared dependency staysHigh: operations, versioning, latency
Language module with real visibilityHigh — it does not compileDepends on what the language offers
Architecture test in CIHigh — it does not mergeLow: maintaining the rule
Static dependency analysisMedium to highLow
Code reviewMedium — depends on who reviews and on their attentionContinuous and human
Documented conventionLowApparently zero, actually high

The table contains the main decision of this document: boundaries that matter deserve an automated mechanism. Code review is a net whose holes vary in size, and the size grows with schedule pressure — exactly when it most needs to work.

The architecture test

The mechanism with the best effect-to-cost ratio for most teams: a test that fails when a forbidden dependency appears.

test: "domain does not depend on infrastructure"

for each class in com.example.domain
no import of com.example.infra
org.springframework
jakarta.persistence

It is cheap to write, runs in seconds, and converts the boundary from a verbal agreement into a verifiable condition. When someone needs to violate it, they have to change the test — which turns the violation into an explicit decision, debatable in review, rather than an import that slips through.

That is the idea that reappears at Level 07 as a fitness function.

Mental Model

The actual architecture of a system is its dependency graph. Everything else is commentary on it.

Faced with any claim about a system's architecture, the verification is always the same: extract the real dependencies and compare. The tool varies by language; the question does not.

Why This Matters

Because the actual architecture is the one that charges you. The cost of change, failure propagation and difficulty of testing all derive from the real graph, not the intended one. A system documented as decoupled and implemented as coupled has all the costs of coupling and none of its benefits.

Because the code is the documentation the next developer reads. Newcomers learn the architecture by imitating what they find. If what they find violates the diagram, the diagram has lost — and every new violation looks consistent with what already exists.

Because deciding a boundary without a mechanism is deciding half. Choosing that the domain does not depend on infrastructure and not enforcing it produces the cost of the decision (indirection, more files) without the benefit (real independence). It is the worst of both worlds, and it is common.

Common Mistakes

Trusting a directory convention as a boundary. A folder called domain prevents nothing. It signals intent, which is useful, and enforces absolutely nothing.

Believing code review is enough. Review catches what the reviewer looks for, on the day they are attentive, in a diff they can read in full. None of those three conditions is reliable under deadline.

Treating a violation as a personal failure. If three different people crossed the same boundary, the problem is not the three people. Either the boundary is in the wrong place, or it has no mechanism. Both are design questions.

Discovering drift only at the next big refactoring. Without continuous measurement, the divergence is discovered when someone attempts a large change and fails — which is the most expensive possible moment to discover it.

Enforcing too many boundaries. The opposite mistake, and also real. Every effective boundary has a cost: indirection, ceremony, friction. A system with fifteen enforced boundaries where three would do is as dysfunctional as one with none. Enforce the ones that matter — and having to choose which is precisely the architectural work.

Real-World Example

A team adopts Hexagonal Architecture on a new service. Diagrams, documentation, a presentation to the department. Six months later, the service has eight use cases and the promised directory structure.

A dependency analysis reveals: four of the eight use cases import the payment service's HTTP client directly, bypassing the port that existed for it. The entities carry ORM annotations. Two adapters import each other.

The system has the indirection of Hexagonal — ports, adapters, more files — and does not have the property the indirection was supposed to buy: replacing the payment client still touches the domain.

The team responded with two architecture tests, written in one afternoon: no domain package imports infra, and no adapter imports another adapter. Both failed immediately, with nineteen violations.

The instructive part: the nineteen were fixed in three weeks, and no new ones appeared afterwards. The problem was never capability or discipline — it was the absence of a signal. While violating was silent, violating happened.

Practical Exercise

Pick a boundary your system claims to have — a layer, a module, a rule of "this does not access that".

Write a test that fails if it is violated. Do not fix anything yet: run it and count the violations.

Two questions: did the number surprise you? And, for each violation, is it a shortcut to fix or a signal that the boundary is in the wrong place?

The second question is the more valuable one. Not every violation is a mistake by whoever wrote it; some are the system telling you the boundary was badly drawn.

Interview Questions

  • How do you verify that the documented architecture is the one the system has?
  • What do you do when you find a systematic violation of a boundary?
  • Which mechanisms make an architectural boundary effective, and how do you choose among them?

Further Exploration

  • Ford, Neal; Parsons, Rebecca; Kua, Patrick. Building Evolutionary Architectures. O'Reilly, 2017 — fitness functions as a mechanism.
  • Documentation for ArchUnit (Java) and equivalents such as import-linter (Python) and dependency-cruiser (TypeScript) — implementations of the architecture test.
Finished reading this document?Your progress is saved in this browser only.