This topic follows Kestrel Energy, a fictional Dutch energy supplier. Its customer portal shows usage and invoices, meter-api receives a reading from each smart meter every 15 minutes, and the billing system KBS (Kestrel Billing System) calculates invoices and runs a nightly export at 02:30. Most of the decisions on these pages are about those three systems and the PostgreSQL database server db01 they share. (This is a simplified model: in the Netherlands a smart meter actually reports to its grid operator, at most once a day, and suppliers receive the readings from there — see Smart Meters and the P1 Port.)

What architecture is

There is no single official definition, and the useful ones are short. Ralph Johnson, quoted by Martin Fowler in his 2003 column "Who Needs an Architect?", describes it as what expert developers have in common: "This shared understanding is called 'architecture.'" He also gave the most honest definition: "Architecture is about the important stuff. Whatever that is."

Fowler drew a practical test from that: architecture is "things that people perceive as hard to change." The formal standard, ISO/IEC/IEEE 42010, says the same in more words; its 2011 edition defines architecture as the "fundamental concepts or properties of a system in its environment embodied in its elements, relationships, and in the principles of its design and evolution." (The 2022 edition widened "system" to any "entity of interest".)

Put together: software architecture is the set of important, hard-to-change decisions about a system: which parts it has, what each part is responsible for, how the parts communicate, where the data lives, and which qualities (speed, availability, security, ease of change) the structure must deliver. The code is the result of those decisions; the architecture is the reasoning that shaped it.

Architecture and design

Software design is deciding how code is organized to do its job: which classes and functions exist, what they are called, which data structures and algorithms they use. Architecture is the part of design that is expensive to undo. Grady Booch (2006) put it this way: "All architecture is design but not all design is architecture. Architecture represents the significant design decisions that shape a system, where significant is measured by cost of change."

So the line between the two is not a type of decision but its cost. A useful name for the decisions on the expensive side is architecturally significant decision: a decision that is costly to reverse, or that affects many parts of the system or one of its main quality goals. Typical examples:

  • how the system is split into deployable parts;
  • how those parts communicate (calls, messages, files);
  • which part owns which data, and where it is stored;
  • the language, platform and main frameworks;
  • the quality targets, such as how fast, how available and how secure (see Quality Attributes and Trade-offs).

Sorting decisions by cost of change

A rough but honest test asks two questions: how many person-days would it take to reverse this, and how many systems would have to change? Ravi Kumar, the portal's tech lead, and Joost Bakker, who owns KBS, listed a few Kestrel decisions that way:

decisions = [
    # decision, person-days to reverse it, systems that would have to change
    ("Readings every 15 minutes in the meter-api contract", 160, 3),
    ("PostgreSQL as the database for KBS", 120, 2),
    ("Portal, meter-api and KBS share db01", 90, 3),
    ("CSS framework of the portal", 3, 1),
    ("Log format of the portal: one JSON object per line", 2, 1),
    ("Name of a local variable in the tariff code", 0.1, 1),
]


def is_significant(days, systems):
    return days >= 20 or systems >= 3


print(f"{'kind':12}  {'days':>5}  systems  decision")
for name, days, systems in decisions:
    kind = "architecture" if is_significant(days, systems) else "design"
    print(f"{kind:12}  {days:>5}  {systems:>7}  {name}")
kind           days  systems  decision
architecture    160        3  Readings every 15 minutes in the meter-api contract
architecture    120        2  PostgreSQL as the database for KBS
architecture     90        3  Portal, meter-api and KBS share db01
design            3        1  CSS framework of the portal
design            2        1  Log format of the portal: one JSON object per line
design          0.1        1  Name of a local variable in the tariff code

The thresholds are judgment, not science, and the estimates are guesses. The value is in the conversation: the 15-minute interval looks like a small detail in an API, but it is built into hundreds of thousands of meters, the database layout and the invoices. Decisions like that deserve a written record and a second opinion; the CSS framework does not.

Kestrel Energy's architecture today

The picture below is the shape the rest of this topic starts from. Nobody designed it this way on purpose: it grew. Each system was built by a different team at a different time, and connecting them all to one database server was the quickest way to share data.

Kestrel Energy today. Smart meters send readings every 15 minutes to meter-api on api01. The customer portal on web01 and web02 calls meter-api over HTTP. The portal, meter-api and KBS, the billing system on bill-app-01 and bill-app-02 with its nightly export at 02:30, all read and write the same PostgreSQL database server db01. smart meters, every 15 minutes Customer portal web01, web02 meter-api api01 KBS (billing) bill-app-01/02 export at 02:30 HTTP db01 · PostgreSQL shared by all three SQL SQL SQL
Three systems, one database server. Arrows point from the part that uses something to the part it uses.

One way to see which decisions are architectural is to ask what a change would ripple into. The script below walks the "uses" relationships backward from the changed part:

# What each part of Kestrel Energy's landscape uses today
uses = {
    "portal": ["meter-api", "db01"],
    "meter-api": ["db01"],
    "KBS": ["db01"],
    "billing export": ["KBS", "db01"],
}


def affected_by(changed):
    """Everything that may break, directly or indirectly, if `changed` changes."""
    hit, todo = set(), [changed]
    while todo:
        current = todo.pop()
        for part, dependencies in uses.items():
            if current in dependencies and part not in hit:
                hit.add(part)
                todo.append(part)
    return sorted(hit)


for changed in ("db01", "KBS", "meter-api", "portal"):
    print(f"change {changed:9} -> {', '.join(affected_by(changed)) or 'nothing else'}")
change db01      -> KBS, billing export, meter-api, portal
change KBS       -> billing export
change meter-api -> portal
change portal    -> nothing else

db01 touches everything. That is not only a theory: during incident INC-22047 on 6 October 2026, a meter-api release leaked database connections on db01, and because the portal uses the same server, it slowed down too, and KBS would have been refused new connections as well if db01 had reached its limit: one service's bug became everyone's risk (the timeline is on Incident Response in Practice). The shared database did not cause the bug, but it widened the blast radius. Whether and how to change that is a typical architectural question, and later pages in this topic come back to it.

Every decision is a trade-off

An architectural trade-off is a decision that improves one quality of a system at the cost of another, because the two pull in opposite directions. There is almost never an option that is better in every respect; if there were, nobody would need to decide. The shared db01 is a good example:

What the shared database gives What it costs
One place to back up, monitor and tune One place where a fault hurts every system
Joins across billing and usage data in one ACID transaction Every team depends on every other team's tables; renaming a column needs three teams to agree
No extra infrastructure, no data copying The systems cannot be scaled, upgraded or released independently

When the systems were young, those gains were worth more than the costs: three small systems, one DBA (Hamid Rahimi) and no need to scale them separately. With 400,000 meters and a portal that must stay up while KBS runs its export, the balance may have shifted. A trade-off that was right then can be wrong now, which is why the reasons behind a decision matter as much as the decision itself.

Architectural styles

Many systems share the same overall shapes, so those shapes have names. An architectural style is a named, reusable way of structuring a whole system, with known strengths and weaknesses: layered, client-server, event-driven, microservices and so on. Naming the style gives a team a shortcut: "KBS is a layered monolith" says a lot in four words.

Style Shape Typical strength Typical cost
Client-server Clients send requests to a central server Central control of data and rules The server is a bottleneck and single point of failure
Layered Presentation, business logic and data access stacked Easy to understand; the default for business apps Changes often cut through every layer
Pipe and filter Data flows through a chain of processing steps Steps are simple and reusable Poor fit for interactive work
Event-driven Parts react to events instead of calling each other Loose coupling, absorbs load bursts Harder to follow and to debug
Monolith and microservices One deployable unit versus many small ones Simple to run (monolith); independent teams (micro) Hard to change in parts; operational complexity

Real systems mix styles. Kestrel's portal is client-server toward browsers and layered inside; meter-api is a small pipeline. A style is a starting point for a discussion, not a label to live up to.

Essential and accidental complexity

In his 1986 essay "No Silver Bullet", Fred Brooks split the difficulty of software in two. Essential complexity comes from the problem itself: energy billing really does involve tariffs that change over time, advance payments, annual statements and meters that are replaced mid-year. No design can make that go away. Accidental complexity is everything that comes from how the solution was built: three systems that store the same number in three formats, workarounds for an old library, a deployment that needs twelve manual steps.

from decimal import Decimal


# Essential: what a bill means - usage times price, plus a fixed standing charge
def monthly_amount(kwh, price_per_kwh, standing_charge):
    return kwh * price_per_kwh + standing_charge


# Accidental: three systems store the same number in three different ways
def to_kwh(raw, source):
    if source == "meter-api":          # whole watt-hours
        return Decimal(raw) / 1000
    if source == "KBS export":         # Dutch notation, decimal comma
        return Decimal(raw.replace(".", "").replace(",", "."))
    if source == "portal upload":      # text with a unit
        return Decimal(raw.removesuffix(" kWh"))
    raise ValueError(f"unknown source: {source}")


readings = [(287400, "meter-api"), ("287,4", "KBS export"), ("287.4 kWh", "portal upload")]
for raw, source in readings:
    kwh = to_kwh(raw, source)
    amount = monthly_amount(kwh, Decimal("0.27"), Decimal("6.50"))
    print(f"{source:14} {kwh} kWh  EUR {amount:.2f}")
meter-api      287.4 kWh  EUR 84.10
KBS export     287.4 kWh  EUR 84.10
portal upload  287.4 kWh  EUR 84.10

The business rule is one line; the conversions are four times as long and are where the bugs will be. A good architecture keeps accidental complexity small (here: agree on one unit at the boundaries) so that the team's attention goes to the essential part. The opposite also happens: a fashionable architecture can add accidental complexity, such as network calls and data copies, to a problem that did not need them.

Architecture as communication

If architecture is a shared understanding, it only exists as far as it is shared. Most of what goes wrong in practice is not a bad decision but a decision that half the team never heard of: the new developer who adds a query on KBS tables to the portal because "everything is in db01 anyway".

Three habits keep the understanding shared:

  • A few current diagrams, each for one audience: a context view for managers and other teams, a container view for developers and operations. One diagram that tries to show everything helps nobody.
  • Decision records: a short text per significant decision with its context, the decision, and its consequences, numbered and kept (ADR-0001, ADR-0002, …). Six months later they answer "why on earth did we do this?"
  • Documentation next to the code, reviewed in the same pull requests, so it changes when the code changes (docs-as-code).

Documenting Architecture covers the C4 model, decision records and arc42 in detail.

What software architects do

A software architect is the person, or the role shared by several people, responsible for a system's architecturally significant decisions: making them with the team, recording their trade-offs, and keeping the structure aligned with the qualities the business needs. Some organizations have it as a job title; in many teams it is part of the work of senior developers. Kestrel has nobody called "architect": Ravi Kumar takes these decisions for the portal and Joost Bakker for KBS, with Hamid Rahimi as soon as the database is involved.

In practice the work consists of:

  • Deciding, or helping the team decide, the expensive questions, and recording why;
  • Translating business goals ("the portal must be up during the billing run") into measurable quality targets;
  • Communicating the structure, in both directions: explaining it to developers, and explaining its costs to managers;
  • Staying close to the code, enough to know what the decisions feel like in daily work and to notice when reality has drifted from the diagram;
  • Making things easier to change. Near the end of his column, Fowler sets a goal worth remembering: "one of an architect's most important tasks is to remove architecture by finding ways to eliminate irreversibility in software designs." A decision that can be cheaply undone no longer needs to be right the first time.