know.2nth.ai Tech Architecture
tech · Software Architecture · Skill Leaf

The decisions that are expensive to reverse.

Architecture isn't diagrams for their own sake. It's the small set of decisions — how the system is split, where state lives, what talks to what — that are cheap to make on day one and ruinous to undo a year in. The skill is making those decisions deliberately, recording why, and being honest about the trade-offs. This is the discipline every 2nth build draws on: ADRs, the C4 model, trade-off matrices, a working catalogue of patterns, and the judgement to know when to break them.

ADRs C4 model 12 patterns Anti-patterns Trade-off matrices

Architecture is the set of decisions that resist change.

A useful working definition, after Ralph Johnson and Martin Fowler: architecture is the things that are hard to change — and the shared understanding of why they are the way they are. Renaming a function is not architecture. Choosing a single database that every service shares, or splitting a system into twelve deployable pieces, is. The first you can undo in an afternoon; the second you live with for years.

That framing does real work. It tells you where to spend care: not on every line, but on the handful of decisions whose cost of being wrong is high and whose cost of reversal is higher. It also tells you the deliverable. Architecture's output isn't a pretty diagram — it's a decision, its alternatives, and the reasoning, written down where the next person (or the next agent) can find it.

The most common failure isn't picking the wrong pattern. It's making the decision implicitly — nobody chose the distributed monolith; it accreted — and leaving no record of why, so every future change re-litigates settled ground or breaks an invariant no one remembered was load-bearing.

Three questions that define a system's architecture

  • How is it split? One deployable or many; by layer, by feature, by domain.
  • Where does state live? One database or many; who owns which data; how consistency is handled across boundaries.
  • How do the parts talk? In-process calls, synchronous HTTP, asynchronous events — and what happens when a dependency is slow or down.

Three tools carry most of the work.

You don't need a heavyweight framework. Three lightweight artefacts — a decision record, a model that draws the system at the right zoom level, and a matrix that makes a trade-off explicit — cover the vast majority of real architectural work and travel well between humans and agents.

ADRs — Architecture Decision Records. A short markdown file per significant decision, in the repo next to the code. Michael Nygard's original template is four headings: Context (the forces at play), Decision (what we chose), Status (proposed / accepted / superseded), Consequences (what becomes easier and harder). Numbered, append-only, never deleted — a superseded ADR stays as the record of what we believed at the time.

# ADR-014: Use a modular monolith, not microservices

Status: Accepted
Context: 4-person team, one product, deploys daily. Microservices
  would add network failure modes and ops overhead we can't staff.
Decision: One deployable, enforced module boundaries, one database
  with per-module schemas. Revisit if a module needs independent scaling.
Consequences:
  + Simple deploys, in-process calls, easy local dev
  + Boundaries already drawn if we ever split out a service
  − Shared database is a coupling risk — needs discipline

The C4 model (Simon Brown) draws the system at four zoom levels, so a diagram answers exactly one question instead of becoming an everything-map nobody trusts:

Level 1

Context

The system as one box, and the people and external systems around it. The diagram for non-technical stakeholders.

Level 2

Containers

The deployable units — web app, API, database, queue. The single most useful level for most conversations.

Level 3

Components

The major building blocks inside one container and their responsibilities. Drawn only where it earns its keep.

Level 4

Code

Classes / functions. Usually best left to the IDE — rarely worth hand-drawing or maintaining.

Trade-off matrices turn an argument into a table. List the options down the side, the qualities that matter across the top (operability, cost, latency, team fit, blast radius), and fill the cells honestly — including the boxes where your preferred option loses. A decision that survives its own trade-off matrix is defensible; one that can't be written as a matrix usually isn't a decision yet, it's a preference.

Twelve patterns worth knowing by name.

A pattern is a named, reusable shape with a known set of trade-offs. Knowing them by name is leverage: it turns a vague debate into "we're choosing between a modular monolith and event-driven services, and here's why." None is universally right — each is the answer to a specific pressure.

01 · structureMonolith
02 · structureModular monolith
03 · structureMicroservices
04 · messagingEvent-driven
05 · dataCQRS
06 · dataEvent sourcing
07 · structureHexagonal
08 · structureLayered
09 · computeServerless
10 · computeEdge-first
11 · migrationStrangler fig
12 · integrationBFF + Saga
01 · structure

Monolith

One deployable, one codebase. Simplest to build, test and reason about.

Use: most new products, small teams.
02 · structure

Modular monolith

A monolith with enforced internal boundaries. Simplicity now, clean seams for later.

Use: the default first move in 2026.
03 · structure

Microservices

Many small independently-deployable services. Independent scaling & teams — at real ops cost.

Use: many teams, proven scale need.
04 · messaging

Event-driven

Components react to events on a bus rather than calling each other directly. Loose coupling, async resilience.

Use: workflows, fan-out, decoupling.
05 · data

CQRS

Separate the write model from the read model. Optimise each independently.

Use: heavy reads, complex reporting.
06 · data

Event sourcing

Store the sequence of changes, not just current state. Full audit, time-travel — and complexity.

Use: audit-critical, finance, ledgers.
07 · structure

Hexagonal

Ports & adapters: domain logic at the centre, I/O at the edges behind interfaces.

Use: long-lived, testable cores.
08 · structure

Layered

Presentation → application → domain → data. The familiar default; risks anaemic domains.

Use: CRUD-heavy line-of-business apps.
09 · compute

Serverless

Functions that scale to zero; no servers to manage. Pay-per-use, cold-start trade-offs.

Use: spiky, event-shaped workloads.
10 · compute

Edge-first

Run compute close to the user (Workers, CDN edge). Low latency, constrained runtime.

Use: global latency-sensitive apps.
11 · migration

Strangler fig

Wrap a legacy system and replace it route-by-route until the old one withers.

Use: replacing a system you can't stop.
12 · integration

BFF + Saga

Backend-for-frontend per client; sagas coordinate multi-service transactions without a global lock.

Use: multi-client UIs, distributed writes.

The 2026 default, stated plainly

For most new products with a small team, start with a modular monolith and reach for an edge-first or serverless deployment surface. You get simplicity now and clean module boundaries that make extracting a service later a refactor, not a rewrite. Microservices are a response to organisational scale (many teams shipping independently) far more often than technical scale — adopting them before you have that problem buys you distributed-systems pain with none of the payoff.

The shapes that look fine until they don't.

An anti-pattern is a recurring "solution" that creates more problems than it solves. They're worth naming because they're rarely chosen — they accrete — and a name is what lets a team say "that's a distributed monolith" before it's load-bearing.

Anti-patternHow you spot itThe fix direction
Distributed monolithMicroservices that must deploy together and share a databaseMerge back, or sever the shared data and the lock-step releases
Big ball of mudNo discernible structure; everything reaches into everythingIntroduce boundaries incrementally; strangler-fig the worst parts
Golden hammerOne tech (k8s, GraphQL, microservices) applied to every problemMatch the tool to the pressure; matrix the alternatives
Premature microservicesTwelve services, one team, weekly cross-service changesCollapse to a modular monolith until scale forces a split
Anaemic domainObjects with data but no behaviour; all logic in "services"Move invariants onto the domain; thin the service layer
Chatty integrationN+1 network calls to render one screenBatch, a BFF aggregation layer, or denormalise the read model

The tell they share

Almost every anti-pattern is a coupling problem wearing a decoupling costume. The distributed monolith bought network calls and got tighter coupling. Premature microservices bought independence and got lock-step deploys. When something feels harder than it should, look for the place two things that should move independently are quietly chained together — that's usually the root, and the diagram rarely shows it.

When to invest in architecture — and when to break the rules.

Architecture is a cost, paid in time and constraint. Like any cost it's worth paying only where the return is real. The skill includes knowing when not to architect — and when a clean rule should be broken on purpose, with the reason written down.

Invest deliberately when

  • The decision is expensive to reverse (data model, system split)
  • Multiple teams must agree on a contract or boundary
  • Compliance or audit needs a defensible record of why
  • The blast radius of getting it wrong is large
  • You're replacing something you can't switch off
  • The system will outlive the people building it now

Breaking a rule honestly

Good architecture isn't dogma — it's owning the trade-off out loud. Shipping a shared database across two services to hit a deadline can be the right call. The discipline is to write the ADR that says you did it, why, and what would trigger undoing it. A documented, deliberate violation is engineering. The same shortcut taken silently is the first plank of the next big ball of mud. The rule isn't "never break the rule"; it's "never break it without leaving a note."

Architecture under real constraints.

SA delivery sharpens the trade-offs. Intermittent power and connectivity, a weak currency against USD-billed cloud, POPIA data-residency questions, and small teams all push architecture toward resilience, frugality and clear ownership — not theoretical purity.

Resilience · power & connectivity

Designs that assume always-on infrastructure fail badly during load-shedding and flaky links. Favour queue-and-retry over synchronous chains, offline-tolerant clients, and edge-first deployment that keeps working when the origin is unreachable. Event-driven shapes aren't an academic preference here — they're how the system survives a 2-hour outage without losing work.

Frugality · FX-exposed cloud spend

Every USD-billed service is a rand risk. Architecture is where you control it: scale-to-zero serverless for spiky workloads, ruthless attention to egress (the hidden tax of multi-cloud), and a bias toward owned, fixed-cost infrastructure where volume justifies it. The cheapest architecture is often the one with the fewest moving parts billed by the hour.

Residency · POPIA & the data map

POPIA makes "where does each piece of data live, and who can reach it?" an architectural question, not a legal afterthought. A clear container diagram that marks data stores by region — af-south-1 / South Africa North for residency-sensitive data, elsewhere for the rest — is half of a compliance conversation done. Draw the boundary before the auditor asks you to.

Where architecture meets the rest of the tree.

Primary sources.

The canonical references behind this leaf — the C4 model, the ADR pattern, and the standard texts on architectural trade-offs. Last reviewed 2026-06-17.