What is software architecture, and how is it different from design?
Software architecture is the set of significant structural decisions about a system: its major elements, their relationships, and the quality attributes those decisions produce. Design is the more local activity of deciding how individual elements are implemented. The practical test for "architectural" is impact and cost of change: a decision is architectural if reversing it later would be expensive or disruptive, such as a choice of consistency model, deployment topology, or module boundaries. Detailed class design, by contrast, can usually be changed cheaply within a single module.
When should I choose microservices over a monolith?
Default to a modular monolith and move to microservices only when you have a concrete, present reason. Microservices buy independent deployment and scaling of parts, team autonomy, and fault isolation, but they impose a heavy tax: network failures, eventual consistency, distributed transactions, and the operational burden of running many services. They pay off when teams are numerous enough to own services autonomously, when parts of the system have genuinely different scaling or availability needs, and when you have the platform maturity (CI/CD, observability, on-call) to operate them. Splitting too early converts cheap in-process calls into hard distributed-systems problems.
How do I decide where to draw service or module boundaries?
Draw boundaries around bounded contexts - areas of the domain where a single model and ubiquitous language stay consistent - rather than around technical layers. Good boundaries have high cohesion inside and loose coupling across, and they tend to align with business capabilities and the teams that own them (Conway's Law). A useful heuristic is that a boundary is right when most changes touch only one side of it; if a typical feature constantly requires coordinated changes across two services, the boundary is in the wrong place. Resist splitting by technical tier (a "user service", "database service") because that maximises chatty cross-boundary coupling.
What are quality attributes and why do they drive architecture?
Quality attributes are measurable non-functional properties - performance, availability, scalability, security, modifiability, observability - that determine whether a system is fit for purpose beyond its features. They drive architecture because, unlike features, they are mostly determined by structure and are very hard to retrofit. You make them concrete with scenarios: a stimulus, the environment, the expected response, and a response measure, for example "under peak load (environment), a checkout request (stimulus) returns within 300 ms at p99 (response measure)". Vague goals like "the system should be fast" cannot be designed for or tested.
What is the difference between Clean, Hexagonal, and Onion architecture?
All three are expressions of the same core idea: keep business logic at the centre and make source-code dependencies point inward, so the domain does not depend on frameworks, UI, or databases. Hexagonal (Ports and Adapters) frames this around ports the application exposes and adapters that implement them. Onion arranges the same idea as concentric layers with the domain model at the centre. Clean Architecture generalises and popularises it with the explicit "dependency rule". In practice the differences are mostly vocabulary and emphasis; choosing one over another matters far less than actually enforcing the inward-pointing dependency direction.
How do I keep my architecture from rotting over time?
Architectural drift happens when expedient changes quietly erode the intended boundaries until the design is unrecognisable. The most effective defence is to make architectural rules executable as fitness functions: automated checks in CI that fail the build when a layering rule, dependency direction, or latency budget is violated (tools like ArchUnit do this for the JVM). Pair that with recorded ADRs so decisions and their rationale are explicit, regular lightweight architecture review, and refactoring budgets so debt is paid down deliberately rather than ignored. Architecture that is only described in diagrams, and never verified, will drift.
Do I need an API gateway, and what should it do?
An API gateway is worthwhile once you have multiple backend services and external clients, because it gives you one place to handle routing, authentication, rate limiting, TLS termination, and request aggregation, shielding clients from your internal topology. Keep it thin: it should handle cross-cutting edge concerns, not business logic, or it becomes a bottleneck and a coupling point that every team must coordinate through. If different client types have very different needs, consider a Backend for Frontend per client instead of overloading one generic gateway. For a single service, a gateway is usually unnecessary overhead.
How should services communicate: synchronous calls or asynchronous events?
Synchronous request/response (HTTP, gRPC) is simpler to reason about and gives immediate results, but it couples caller availability to callee availability and can create latency chains and cascading failures. Asynchronous messaging or events decouple services in time, improve resilience and scalability, and suit workflows that can tolerate eventual consistency, at the cost of harder end-to-end tracing and ordering. A common rule of thumb is to use synchronous calls for queries that need an immediate answer and asynchronous events for state changes that other services merely need to know about. Many systems mix both deliberately rather than choosing one globally.
What is eventual consistency and when is it acceptable?
Eventual consistency means that, after a change, replicas or downstream views will converge to the same value over time but may be briefly out of date. It is the consistency model most distributed and microservice systems accept in exchange for availability and partition tolerance, as framed by the CAP theorem. It is acceptable whenever the business can tolerate a short staleness window - a "likes" count, a search index, a notification feed - which is most of the time. It is not acceptable for invariants that must hold instantly, such as preventing a double spend; those belong inside a single consistency boundary (an aggregate or a single database transaction).
How do I handle transactions that span multiple services?
Do not use distributed two-phase commit across autonomous services; it scales poorly and blocks when the coordinator fails. Use the saga pattern: model the business transaction as a sequence of local transactions, each with a compensating action that semantically undoes it if a later step fails. Sagas come in orchestration form, where a coordinator explicitly drives the steps, and choreography form, where services react to each other's events. Whichever you choose, make each step idempotent so retries are safe, and accept that the overall operation is eventually consistent rather than atomic.
What resilience patterns should every distributed system have?
At minimum: timeouts on every remote call so a slow dependency cannot tie up your threads indefinitely; retries with exponential backoff and jitter for transient failures, applied only to idempotent operations; circuit breakers that fail fast when a dependency is clearly down; and bulkheads that isolate resource pools so one failing dependency cannot starve the rest. Add graceful degradation so the system serves a reduced but useful experience when a non-critical dependency is unavailable. The unifying goal is to contain failure and shrink its blast radius rather than assume dependencies never fail.
How much architecture should I do up front versus emergent?
Decide up front only the things that are expensive to change later and constrain everything else - the consistency model, major boundaries, the deployment and integration style, and the dominant quality-attribute targets. Defer decisions that are cheap to change ("last responsible moment") and let detailed structure emerge through refactoring as you learn the domain. This middle path avoids both big design up front, which bets heavily on incomplete knowledge, and no design at all, which produces a tangle that is costly to untangle. Evolutionary architecture formalises this by protecting the few hard constraints with fitness functions while leaving room to evolve.
What is the C4 model and why is it popular?
The C4 model describes a system with four nested levels of diagram - System Context, Containers, Components, and Code - so each diagram has one clear audience and abstraction level. Its popularity comes from being simple, notation-light, and consistent, which keeps diagrams understandable and maintainable rather than turning into UML sprawl that nobody updates. In practice, most teams get the most value from the Context and Container levels, drilling into Components only for the parts that warrant it. It complements, rather than replaces, ADRs, which capture the "why" that diagrams cannot.
How do I document an architecture so it stays useful?
Favour lightweight, living artefacts over a single heavyweight document that is obsolete the day it is signed off. Record significant decisions as ADRs committed with the code, draw a small number of C4 diagrams at the levels that matter, and write down quality-attribute scenarios so the targets are explicit and testable. Keep documentation close to the code and update it as part of the change that affects it, rather than as a separate ceremony. The aim is documentation that answers the questions newcomers and reviewers actually ask: what are the boundaries, why were they chosen, and what must stay true.
How does Conway's Law affect my architecture?
Conway's Law states that systems end up structured like the communication patterns of the organisation that builds them, because interfaces between teams become interfaces in the software. The practical consequence is that you cannot impose an architecture that fights your org chart and expect it to hold; the boundaries will drift to match how teams actually communicate. The "Inverse Conway Manoeuvre" turns this to your advantage: shape teams around the boundaries you want in the software - for example, one team per bounded context - so the desired architecture is the path of least resistance. Architecture and team design are therefore two sides of the same decision.
What is technical debt and how should I manage it architecturally?
Technical debt is the implied future cost of an expedient choice over a sounder but slower one; it accrues "interest" because the shortcut makes future change harder. The key distinction is between deliberate, recorded debt taken as a conscious trade-off and reckless, hidden debt that silently degrades the system. Manage it by making it visible: record it (often as ADRs or a debt register), estimate its carrying cost, and pay it down opportunistically when you next touch the affected area. Architecture debt is the most dangerous kind because, unlike a local code smell, it is woven into boundaries and contracts that are expensive to change.