Principles#
Architecture is the lab’s discipline. The operator who designs the system designs how it survives contact, and a small handful of timeless principles guide most of the decisions worth making. The hard part is not memorising them; it is recognizing which one applies under the pressure of a deadline, an outage, or a change in mission.
The principles below (separation of concerns, cohesion, coupling, SOLID, and the rest) are the same ones the IC and SOF apply to their tools and their tradecraft. The operator borrows them to build capability that lasts past one campaign.
Separation of Concerns#
Each module should be responsible for one well-defined aspect of the system. The principle is older than software (physical engineering uses it for the same reason) and most architectural problems trace back to violating it. The classic split.
Presentation, UI, API surface, serialization.
Application, use cases, orchestration of domain operations.
Domain, business rules and invariants, independent of frameworks.
Infrastructure, databases, queues, external services.
Layers depend inward: Infrastructure → Application → Domain. The domain shouldn’t know about HTTP or SQL.
Cohesion#
Things that change together belong together. A module is cohesive when its contents serve a single purpose. Low cohesion (a “utils” module that grows forever) is a leading indicator of trouble.
Coupling#
Coupling is how much one module depends on another. The cost of high coupling is paid every time you change anything: the dependencies cascade and the test surface explodes. Reduce it where possible by.
Programming to interfaces, not implementations.
Pushing dependencies inward (dependency inversion).
Hiding implementation details behind a stable contract.
Some coupling is inevitable. The goal is to localize it; a few seams between subsystems beats a thousand tangled threads.
SOLID#
Object-oriented design heuristics that generalize beyond OO. Each letter is a separate principle; together they’re the most-cited shorthand for “write code that doesn’t fight you when requirements change.” None are absolute; they pull against each other and against simplicity.
Single Responsibility, one reason to change per class.
Open/Closed, open for extension, closed for modification.
Liskov Substitution, subtypes must be usable as their base type.
Interface Segregation, many small interfaces beat one giant one.
Dependency Inversion, depend on abstractions, not concretions.
DRY, KISS, YAGNI#
Three short heuristics that show up in every codebase culture discussion. Each one is true in its own context; they pull against each other in practice (DRY can over-abstract; KISS can under-design), which is why they’re a conversation rather than a checklist.
DRY (Don’t Repeat Yourself), duplicated knowledge is the problem; duplicated code that happens to look alike is fine.
KISS (Keep It Simple, Stupid), the simplest thing that solves the problem usually wins.
YAGNI (You Aren’t Gonna Need It), don’t add machinery for futures you can imagine but can’t justify.
These pull against each other; that’s the point. They’re a conversation, not a checklist.
Conway’s Law#
Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.
A microservices boundary that doesn’t match a team boundary will heal back into a distributed monolith. Architect the org alongside the system.
Boring Technology#
Choose your innovation tokens carefully. Most systems should be built with boring, well-understood, well-supported tools, with one or two strategic deviations where they actually pay off. The deeper an organization gets into novel technology choices, the more time it spends on integration glue rather than the product.
Reversibility#
Decisions vary in cost-to-undo. Two-way doors (most code, deployments, config); decide quickly, fix later if wrong. One-way doors (data formats, public APIs, technology selections, organization boundaries); slow down, write things up, get more eyes on it.