This is the checklist I return to when I design a system or review an architecture. It is not a methodology. It is a set of considerations from a personal viewpoint.
1. Functional Architecture: Horizontal layers
Start with functions and capabilities of the system or platform. Who are the users or customers? Reference models provide a shared vocabulary and a map of the territory. Four categories cover most ground:
- Business architecture. The capabilities, processes, and outcomes the system exists to deliver. What the business does, independent of any technology.
- Application architecture. The services and components that realize those capabilities, and how they interact.
- Data architecture. The entities the system owns, how they relate, and how they flow and are governed across it.
- Infrastructure architecture. The compute, network, and storage the rest runs on, on-premise or in the cloud.
The application architecture has three parts worth separating:
- Configuration. The tuning knobs: what runs and how it is set up.
- Control flow. The business logic that decides what happens.
- Data flow. How data moves through the system, and where it goes.
Keeping these distinct stops a common failure: business logic hard-coded into configuration, or data assumptions buried in control flow, where nobody can find them when the rules change.
2. Second the non-functionals
The Twelve-Factor App is a good baseline for the build-and-run side of this.
- Scalability. Autoscaling or manual? Autoscaling absorbs spikes but costs more and hides waste. Manual scaling is cheaper to run but slow to react. Scaling is rarely instantaneous.
- Installability. Source control, CI/CD, and push-button deployments.
- Security. At every layer. Network segmentation, both macro and micro. Least privilege at each boundary. Security is not a layer to add. It is a property of every layer.
- Privacy and governance. CCPA and GDPR considerations. Plan for data lineage, access controls, data quality, and privacy controls.
- Performance. Benchmark throughput and latency. Numbers, not adjectives.
- Upgradeability. What does it cost to upgrade or evolve the system? Include license terms.
- Disaster recovery and failover. RTO and RPO against cost. How much data can you lose, how long can you be down, and what are you willing to pay to shrink both?
- Availability. Pick a target and state it. 99.9, 99.99, or higher. Each extra nine is an order of magnitude more expensive. Match the SLA to what the business actually needs.
- Maintainability. The cost and effort of the next change. Most of a system's lifetime is maintenance, so this is most of the cost.
- Audit and logging. Errors, warnings, and security audit trails. You cannot investigate what you did not record.
- Observability and reliability. Can you tell what the system is doing right now, and can you trust it to keep doing it? MTTD, MTTR metrics.
- Affordability and total cost of ownership. Build versus buy. Cloud opex versus on-premise capex.
You will not max out every one of these. The point is to make the trade-off on purpose. An unstated non-functional is a decision made by accident.
3. Align with the organization's strategy
If your organization has a technology or software vision, your design should pull in the same direction. A locally optimal architecture that fights the company's platform strategy is a future migration waiting to happen. This is not about compliance for its own sake. It is about not building the one system that has to be rewritten when the broader strategy lands.
4. Let teams move independently
The goal of a good decomposition is that a team can test and deploy its component or service with little or no coordination from other teams.
Two design choices make independence real:
- Extensible data contracts. The agreement between a producer and a consumer must allow each side to upgrade without breaking the other. Additive, versioned, tolerant of fields it does not recognize. A brittle contract turns two independent teams into one coupled team.
- Orchestration versus choreography. Inside a domain, orchestration works well: a central coordinator drives the steps, and the logic is easy to follow. Across domains, choreography scales better: services react to events without a central conductor, so no one team owns the cross-domain flow. Use orchestration within a bounded context and choreography between them.
5. Design modules to hide what changes
General guidance for modularity Parnas, from 1972: begin with a list of the difficult design decisions, or the decisions most likely to change independently. Then design each module to hide one of those decisions from the others. Modules are not chapters of a story. They are firewalls around the things you expect to revise.
A corollary I lean on: when two alternatives deliver roughly the same value, take the one that makes future change easier. Cheap optionality is worth buying.
A few more principles belong here:
- Conway's law. Your system comes to mirror your communication structure whether you plan for it or not. If two modules must talk, make the boundary as explicit as if they talked through an API. See also hexagonal architecture, which pushes the same idea: keep the core logic independent of the things around it.
- The end-to-end argument. Saltzer, Reed, and Clark make the case that some functions belong at the endpoints of a system, not in its middle. Pushing a guarantee into a lower layer often cannot make it complete, costs everyone more, and is harder to achieve.
6. Categorize your decisions
Not every design decision deserves the same care. Sort them along three axes, and the right amount of effort becomes obvious.
- Urgency. Can this decision be deferred, or is it needed now? Sometimes more information may be available when a decision is deferred.
- Importance. What happens if you get it wrong?
- Reversibility. Can you back out? Can you make it reversible if it is not? You can make a reversible decision quickly and correct it later. This is the antidote to analysis paralysis.
7. Go API-first and SaaS-style
Treat every service, including UI applications, as something reachable through an API. Going API-first forces clean contracts and makes the system composable. Design the API before the rest of the functionality. Ensure a SaaS posture is a property of the system when applicable: capabilities are exposed as services, the system is multi-tenant by design, and anything that can be a self-service API is one. A platform that needs its owners in the loop for every new user does not scale to many users.
8. Transition Architecture: Make execution part of the design
Decide the rollout strategy alongside the architecture.
- Work in small steps. Find out where you are, take a small step toward the goal, adjust on what you learn, and repeat. Big-bang delivery hides risk until the worst possible moment.
- Ship incrementally behind feature flags. Decouple deploy from release. Get changes into production early and dark, then ramp them under control. This shortens the feedback loop and shrinks the blast radius of any one change.
9. Govern with light templates
Governance fails when it becomes paperwork and fails differently when it is absent. The middle path is a few standard templates and a process for using them.
- Use a shared notation. The C4 model gives you context, container, component, and code diagrams at consistent levels of zoom. A custom template is fine too. The point is that everyone draws the same kind of picture.
- Record decisions. Architecture Decision Records capture what you decided, the alternatives, and the reasoning. The reasoning is the valuable part. It is what tells the next person, possibly you, why the obvious-looking change is a trap.
- Maintain reference architecture templates. Reusable starting points keep teams from re-deciding the settled questions.
- Follow an architecture communication process. A design nobody reviewed and nobody can find is not governed. It is just a file.
Closing
Use this as a set of lenses to hold up to a design and spot what is missing. The non-functionals you did not name. The decision you treated as irreversible when it was not. The module boundary drawn around a story instead of around a thing that changes. The contract that quietly coupled two teams.
Good architecture is mostly the discipline of asking the boring questions before they become expensive ones.