Think in Systems
Design interconnected systems, not isolated features.
Overview
Every change lives inside a larger system. We reason about boundaries, data flow and second-order effects before optimising any single part.
Why
Some decisions are not one decision among many, they are the decision every later one is downstream of. How a system isolates its customers determines what its configuration can be, where its release process gates, what its storage keys contain, and whether an entire class of failure is carefully avoided or structurally impossible. That is not something a later refactor reaches. The same applies at smaller scale. Any operation touching two systems has a correct order, and the correct order is the one where every possible crash point leaves recoverable state. Chosen by accident, it is a recovery strategy chosen by accident.
Standard
Identify the decisions everything else descends from, make them first and deliberately, and expect to pay for them somewhere other than in code. Name the crash-safe sequence for every operation spanning two systems, write down the recovery state at each failure point, and test the failure at each one. Branch on what a component can do, never on what it is called, and let a capability describe the instance actually in front of you rather than the vendor's brochure. Separate concerns by lifecycle, not by category. What looks like one thing called "configuration" is usually several, with different owners, different mutability and different blast radius.
What This Forbids
- Multi-system writes whose order is incidental rather than chosen
- Branching on a provider name, plan name, environment name or role name instead of a capability
- Cleanup paths that are not idempotent
- One configuration type covering values with different owners and different blast radii
- Recovering a property by parsing it back out of a stored identifier, when the property could simply be carried
Examples
- Choosing physical isolation per customer over a shared database with a tenant column. It cost an operational procedure per customer and left no cluster to hide behind. It bought the structural impossibility of one customer reading another's records, and, unplanned, a natural staged rollout, because the first customer to receive a release is a canary.
- Writing bytes before the row that references them, with a compensating delete: a failed row insert must not leave an unreferenced object that costs money and may hold sensitive data nothing knows to clean up. On delete, bytes first again, keeping the row, so the file stays tracked and the delete can be retried.
- Separating configuration into four kinds with four lifecycles: an identity that must never change because it is written into stored addresses, a profile that may change freely, environment settings that change per deploy, and secrets that change on rotation. Contact details were later moved out of environment variables into committed profile configuration once the boundary was named.
- Deciding to branch on a component's declared capabilities rather than its name, so that a second implementation offering the same ability keeps working, and letting the capability describe the configured instance, so a provider without a public address reports that it has none.
Counter Examples
- Treating a storage key prefix as an access boundary. The design was internally consistent and had been reviewed as correct; the underlying service grants public access per container rather than per prefix, so the boundary had to be rebuilt a level up.
- Four subsystems each answering "should this process refuse to start?" in its own way. One idea, four implementations, in a single codebase, none of which could report their problems together.
- A seam that accepts a parameter and ignores it, with a comment conceding that a record pointing at the old location will silently read as missing. A seam that lies is worse than no seam, because someone will eventually trust it.
Lessons Learned
- The isolation model is not a deployment detail. It determines what the rest of the system can be, and it is paid for in operations rather than in code.
- A security property derived from an assumed dependency behaviour is a hypothesis until the dependency's own documentation is read. This happened twice, in unrelated subsystems, and review caught neither.
- The most valuable boundary in a two-runtime system is not the one between frontend and backend. It is the one drawn around the set of facts they must not disagree about, and refusing to put anything else inside it is what stops that set becoming a junk drawer.
- An architecture that cannot place a concept is missing something. The concept is the evidence, not the error.
Learned From
- DentraOne deployment per customer, chosen for a failure mode that shared-database tenancy cannot fully eliminate, and every later design decision falling out of it.
- DentraA storage layer that separated four fused concerns: where bytes live, what they are called, what is known about them, and who may see them.
- ZothKitA dependency law with exactly one permitted direction, and the discovery that the packaging did not express it.
Related Principles· 1
Related Documents· 3
Referenced By· 11
Version History
- v0.1.0
Principle established with placeholder content.
- v0.2.0strengthened
Written from real experience. Added the crash-safe ordering rule, capability-over-identity, lifecycle separation, and the finding that the isolation model is upstream of everything else.