Constraints Shape Architecture
Given the same requirements, two different sets of constraints produce two different correct architectures. The store with six weeks, two developers and tens of users is a monolith on one server; the same store with a partner sending a launch spike and a platform team is something else. Neither is the "right" architecture — each is the right one for its constraints.
The situation, the reflex, and why it stalls
Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.
How do the constraints turn into an architecture, and how do you know whether a design decision came from a constraint or from taste?
You have the requirements and the seven constraints written down. A colleague has drawn an architecture: an API gateway, three services, a message queue, a cache, a search index. You have drawn a monolith with one database. Both of you believe you are right, and the requirements support both.
Argue about the architectures. Microservices versus monolith, queue versus synchronous, cache versus not — each side citing what it has seen work elsewhere. The argument is about the diagrams, and the diagrams are about other projects.
The argument cannot be settled because neither diagram is wrong in general; both are right somewhere, and "somewhere" is exactly what the constraints describe and the argument ignores.
- The argument cannot be settled because neither diagram is wrong in general; both are right somewhere, and "somewhere" is exactly what the constraints describe and the argument ignores.
- The compromise is a hybrid: two services instead of three, a queue "for later". It fits neither set of constraints and costs more than either design.
- The design that wins is the one argued more confidently, and the confidence came from a previous project with a different team, a different deadline and a different user count.
- Every later change to the design is argued the same way, because the record shows two diagrams and no reasons.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Trace each piece of a proposed architecture back to a constraint or a requirement, and remove the pieces that trace to neither. A queue traces to "work that must not block the response and must survive a crash" — which is a requirement — or to "expected load exceeds what one process can handle synchronously" — which is a user-count constraint; if neither is on the list, the queue is taste. A service boundary traces to "a team that must deploy independently" or "a component with different scaling or a different required technology"; with two developers and one deployment it traces to nothing.
- Do it in both directions. From the constraints: what does each one force? Six weeks forces few pieces; two developers force one deployment they can both run; tens of users forces nothing; a contracted provider forces its integration shape; the budget forces a ceiling on managed services; the law forces a region and a deletion path. Then from the architecture: what does each piece cost in time, people, money and operational surface — and which constraint pays for it?
- Write the mapping down as the architecture's reasons, so that the design is "a monolith because six weeks and two developers", not "a monolith". When a constraint changes — a third developer, a partner sending traffic, revenue lifting the budget — the mapping says which piece to reconsider, and the reconsideration is a decision rather than a fresh argument (Architecture From Requirements).
- Accept that the same requirements with different constraints produce a different architecture, and that the other one is not wrong. The colleague's diagram is the store built by a platform team with a launch partner and a year; it is a correct answer to a different question, and saying so ends the argument better than winning it.
The store, with its reasons
The diagram is the store's architecture as the constraints produced it. What matters is not the boxes — there are few — but that each edge and each absence has a reason on the list. The pieces that are not here are not forgotten; they are on the trigger list with the constraint change that would bring each one in.
From a constraint to a piece of the design
The pipeline is the trace run forward. It is short because each step is a question, and the failure of each step is the way architecture arguments usually go wrong: a piece with no constraint, a constraint with no piece, or a trigger nobody watches.
- 1Name the constraint
One of the seven, with its number, source and hard/soft mark.
fails by An adjective — "lots of users" — that can be traced to anything.
- 2What does it force?
The pieces, regions, integrations or limits the constraint requires: hosted fields from the provider, a region from the law, one deployment from two developers.
fails by Forcing what the author wanted anyway and calling it the constraint's doing.
- 3What does it forbid?
The pieces the constraint rules out: three services with two developers, a managed search service under the budget, a cheaper region under the law.
fails by Only ever asking what is forced, so the design grows and never shrinks.
- 4Cost what remains
Time, people, money, operational surface per piece — and the constraint that pays for each.
fails by A piece nobody pays for, kept because it is already drawn.
- 5Write the trigger
For each piece not built: the constraint change or measurement that would justify it, and the piece it would justify.
fails by A trigger nobody watches; the piece arrives late, in an outage.
Run it once per constraint, then once per proposed piece in the other direction. The two passes should agree; where they do not, one of them is taste.
The first slice under these constraints
The slice below is what the constraints make the first thing to build: not the catalog, but the deployment and the provider, because six weeks and a contracted provider make those the two things most likely to surprise. What it does not prove is the point — a walking skeleton that reaches the provider says nothing about the store working, and it is still the right first slice for this constraint set.
- DeploymentOne container on one host in the permitted region, deployed by a pipeline both developers have run.
- PageShows one product from the database and a Pay button that renders the provider's hosted fields.
- APIReturns the product; creates a test-mode charge for it; receives the provider's webhook and logs it.
- DatabaseOne product row; one row recording the webhook it received.
How to do it
Most important first.
- For every box and arrow in a proposed architecture, write the requirement or constraint it traces to. Boxes that trace to nothing are candidates for removal; boxes that trace only to "we might need it" are removed (Add Complexity Only When Required).
- For each of the seven constraints, write what it forces and what it forbids. Check the architecture against both lists.
- Cost every piece in the four currencies — time to build, people to run, money per month, and operational surface when it fails — and name the constraint that pays for each (The Complexity Ledger).
- Write the architecture as sentences with reasons, not as a diagram alone. "One deployment, because two developers and six weeks" survives the argument; the diagram does not.
- When a constraint changes, re-run the trace for the pieces that cited it. Do not redesign; reconsider the specific decisions the lapsed constraint made (When Assumptions Change).
Worked on a concrete problem
The move has to produce something. This is what it produced.
- The store, traced. One application process: six weeks, two developers, tens of users — nothing forces more. One database: same. Object storage for images: the data constraint (images are the large thing) and the budget (storage is cheap, database disk is not). Hosted payment fields and webhook handling: the required provider and the card-data law. Deployment region: the data-protection obligation. A scheduled job for the nightly report: a functional requirement, run inside the same process because nothing forces a separate one. Not present: gateway, services, queue, cache, search index — each traced to nothing on the list, each written down with the constraint change that would bring it back.
- The colleague's architecture, traced against different constraints — the ones it was actually designed for, from their last project. Three services: three teams deploying independently. A queue: a partner sending a launch spike whose order writes had to be absorbed. A cache: measured read load from a catalogue a hundred times larger. A search index: a functional requirement for full-text search over that catalogue. Every piece traced — to a constraint the store does not have. The argument ended there.
- The trigger list that came out of it, written next to the monolith: a second deployment when a component needs a different scaling profile or a different required technology; a queue when a measured spike makes checkout wait or when email sending starts failing checkouts; a cache when the product pages are measured slow under real traffic and the database is the reason; a search index when the catalogue outgrows filter-by-name or full-text search becomes a requirement. Each trigger is a constraint change or a measurement, and each names the piece it justifies.
- The chat app under two constraint sets, for contrast. A solo learner with no deadline: one process, one database, a websocket connection per client, messages in a table — realtime is a requirement, but nothing forces a message broker. The same app for a team with a required identity provider, a platform they must deploy on and an expected launch of many concurrent users: the identity provider shapes auth, the platform shapes deployment, and the concurrent-user estimate — if it is measured rather than hoped — justifies separating the connection-holding process from the rest. Same requirements, two correct architectures.
How you know it worked
What now exists that did not before, and what question you can now ask.
- Every piece of the architecture has a written reason that names a requirement or a constraint, and the pieces that had none are gone.
- The architecture argument ended by naming the constraints each design was for, not by one side winning.
- A trigger list exists beside the design, and each trigger is a constraint change or a measurement that would justify a specific piece.
- Someone with the constraints and the requirements — but not the diagram — could reconstruct most of the architecture from the reasons.
The questions you can now ask
The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.
- ?For this box or arrow: which requirement or constraint forces it, and would that constraint force it for someone who did not already want it?
- ?What does each of the seven constraints force and forbid here, and does the design respect both lists?
- ?Which constraint pays for this piece's time, people, money and operational surface?
- ?If the argument is between two architectures, which constraints is each one actually designed for — and are they ours?
What can go wrong
- The trace becomes a justification exercise. Every piece the author wanted is traced to some constraint, however tenuous; "a cache because users" is not a trace, it is a rationalisation. The test is whether the constraint would have forced the piece for someone who did not already want it.
- Constraints are used to reject everything. A team that traces "nothing forces it" for every proposed piece ends up with a monolith that a real constraint — a partner spike, a platform requirement — genuinely did force something for, and ignored.
- The trigger list is written and never watched. The user count crosses the threshold, the measurement is never made, and the queue that the trigger justified is added six months late, in an outage.
- The move is applied to an existing architecture as if it were greenfield. The current design traces to constraints that have lapsed, and the "trace" becomes a demand to rebuild everything at once rather than to reconsider the specific pieces the lapsed constraints made.
- Tracing every piece takes longer than drawing the diagram and is less fun; on a team that shares its constraints implicitly, much of the trace is already in everyone's head.
- An architecture that cites its constraints is one that has to change when they do, and a team that wrote "monolith because two developers" has to revisit it when the third arrives — the reflex design would have quietly stayed.
- Saying "your architecture is correct for different constraints" is more honest and less satisfying than winning; some colleagues hear it as being told their experience does not apply.
- "Constraints mean the monolith always wins." They mean the design that fits wins. The colleague's three services were correct for three teams and a launch partner; the monolith is correct for two developers and six weeks. A team that always concludes "monolith" has stopped tracing.
- "Requirements decide the architecture." Requirements decide what the system does; constraints decide how it may be built. The same requirements produced two correct architectures in the worked example, and only the constraints told them apart.
- "Once traced, the architecture is settled." It is settled until a constraint changes, and constraints change. The trace is what makes the next change a decision instead of an argument — it does not prevent the change.
Where this applies
Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALEvery piece of an architecture should trace to a requirement or a constraint, in any domain; for a data pipeline the constraints are data volume, latency to freshness and the platform, for a compiler they are the target and the team, and the trace is the same move.
- STAGE-SPECIFICOn a greenfield project the trace runs forward from constraints to design; on an existing system it runs backward from the current design to the constraints that made it, and the interesting output is the list of pieces whose constraint has lapsed — those are the candidates for change, one at a time.
- ILLUSTRATIVEThe two architectures, the colleague's previous project and the trigger thresholds are invented to show the trace; the seven constraints are real inputs and the mapping is the method.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — The architecture growth lab at /thinking/grow is the trigger list run as a simulation: raise the user count and see which piece the measurement justifies, and which it does not.