Grokking Modern Software Architecture Styles
Vote

0% completed

​

What Software Architecture Is

Know the Vocabulary

Part 1: A Working Definition of Software Architecture

The Food-Delivery App

Part 2: Architecture Versus Detailed Design

The Three-Question Test

Sorting Ten Decisions from the Food-Delivery App

A Detail That Grows into Architecture

Part 3: Why Architecture Decisions Matter

The Dispatch Story

Part 4: Architecture Styles

The Fifteen Styles in This Course

Part 5: The Architect's Job

FAQs

Key Takeaways

Know the Vocabulary

This lesson uses the terms below repeatedly. Read them once now and return here when a later section needs one.

  • Software architecture: The structure of a system: its parts, the job of each part, how the parts communicate, and the decisions about them that are expensive to change later.
  • Architecture decision: A design decision that touches many parts or teams, takes weeks or more to change in production, and is hard to undo.
  • Detailed design: The decisions inside one part, such as its algorithms, class names and indexes, which change in hours or days without affecting other parts.
  • Cost of change: The engineer time needed to change a decision after release, and the number of parts that must change with it.
  • Reversibility: How easily a decision can be undone: by a code change alone, or only with a data migration, an app update or coordinated releases.
  • Module: A group of related code, such as everything about orders, with a defined interface that other code calls.
  • Interface: A declaration of the operations a component provides to other components, with their inputs, outputs and errors, and no implementation. For a class, it lists the methods the class must provide; in Python, an abstract base class plays this role.
  • Deployable artifact: One packaged build of an application, such as a JAR file or a Docker image, shipped to servers as a unit.
  • Coupling: The degree to which a change in one part of a system forces a change in another part.
  • Message broker: A server, such as RabbitMQ or Kafka, that receives messages from producers and delivers them to consumers.
  • Architecture style: A named, reusable set of architecture decisions with known trade-offs.
  • Quality attribute: A measurable property of how well a system does its job, such as how fast it responds or how often it is down, as opposed to what the system does.
  • Constraint: A limit on how a system may be built that is set outside the design team, such as a budget, a deadline, a law or an existing system that must keep working.
  • Trade-off: A choice in which improving one quality attribute makes at least one other quality attribute worse, such as faster responses at a higher cost per request.

A team of six engineers is building a food-delivery app. In its first month, the team makes several hundred design choices. It picks a language for the backend, names the function that computes an order total, decides whether restaurants and couriers share one mobile app, and chooses a database for the orders. One engineer could change most of these choices next week in an afternoon. A few of them, once real customers depend on the app, will take a whole team several months to change.

This lesson is about that second group: what software architecture is, how to tell its decisions apart from detailed design, and why those decisions deserve more care than the rest.

The rest of the course depends on these ideas. Each lesson in the Architecture Styles chapter describes one style as a set of decisions. It then states what those decisions make easier and what they make harder. That description is only useful once you can recognize an architecture decision and estimate what it would cost to reverse.

Part 1: A Working Definition of Software Architecture

Software architecture is the structure of a system and the decisions about that structure that are expensive to change later. The definition has four elements, and each answers a different question about the system.

  1. The parts. What the system is divided into: applications, modules, services, databases and external systems.
  2. The responsibilities. The one job each part does and the data it owns.
  3. The communication. How the parts talk: function calls inside one process, HTTP requests over the network, or messages through a message broker. It also covers whether the caller waits for an answer.
  4. The expensive decisions. The choices behind the first three elements that many parts depend on. Changing one of them later means changing many parts at once.

The fourth element separates architecture from a drawing of boxes and arrows. A diagram shows the parts and the paths between them. It does not show why the parts are arranged that way, or which arrangement would cost the most to change.

The Food-Delivery App

Every lesson in this chapter uses the same system, so the ideas build on one another. Customers order meals in the Customer app. Restaurants accept and prepare orders in the Restaurant app, and couriers pick up and deliver them in the Courier app. All three apps call one backend application over HTTPS.

The backend is divided into six modules, each with one job:

  • Orders creates each order and tracks its status from placed to delivered.
  • Menu stores each restaurant's dishes, prices and opening hours.
  • Payments charges the customer and issues refunds through an external payment provider.
  • Dispatch assigns an available courier to each order that is ready for pickup.
  • Notifications sends push messages and text messages to customers, restaurants and couriers.
  • Accounts registers users and checks who is logged in.

In this first version, all six modules are built into one deployable artifact and run in one process. They call each other through ordinary function calls and store all of their data in one PostgreSQL database. Payments is the only module that talks to the external payment provider.

The diagram below shows these parts and the paths between them. Later lessons in this chapter return to this diagram and change one part of it at a time.

The Food-Delivery App: First Version
The Food-Delivery App: First Version

All four elements are visible here. The parts are three apps, six modules in one backend, one database and one external provider. The communication is HTTPS from the apps, function calls between modules, SQL to the database and HTTPS to the provider. The expensive decisions are the ones that every module or app relies on: one backend application, one shared database and one public API for the apps.

This first version is a monolith, which the Monolith Architecture lesson describes in full. The drawing alone does not say which of its decisions would take months to change, and the next Part gives a test for that.

Part 2: Architecture Versus Detailed Design

Detailed design is the set of decisions inside one part, made behind that part's interface. The algorithm Dispatch uses to choose a courier is detailed design. So are the name of a function in Orders and the index on a database column. Both kinds are design, and they differ in how far a decision reaches and what it costs to change.

The Three-Question Test

Three questions sort a decision into one group or the other. Ask them about the system after release, with real users and real data. Before anyone depends on it, almost any decision is cheap to change.

  1. Scope: how many parts and teams does the decision touch? A decision hidden behind one module's interface touches one part. A decision that every module, or all three apps, relies on touches all of them.
  2. Cost of change: how much work is it to change after release? Count the engineer-days and the parts that must change together. Hours point to detailed design, while weeks or months point to architecture.
  3. Reversibility: can a code change alone undo the decision? Some decisions also need a data migration (copying or reshaping the live data), a new app release or several teams releasing in a fixed order. Those are hard to reverse.

A decision that touches many parts, costs weeks to change and is hard to reverse is an architecture decision. A decision that touches one part, costs hours and is undone by a code change is detailed design. The three answers usually agree. A decision that many parts depend on is costly and hard to reverse for exactly that reason.

The line between the two groups is not sharp, so decisions sit along a spectrum. The diagram below places several decisions from the food-delivery app on it, with an illustrative cost of changing each one after launch.

The Decision Spectrum: Architecture to Detailed Design
The Decision Spectrum: Architecture to Detailed Design

Sorting Ten Decisions from the Food-Delivery App

The table below applies the three questions to ten decisions from the team's first month. The reason column states which answer decided each row.

DecisionKindReason
Run all six modules in one backend application, not as separate servicesArchitectureIt fixes how every module is built, released and scaled; splitting later takes months.
Store all data in one PostgreSQL databaseArchitectureEvery module's queries and transactions assume it; moving data out needs a live migration.
The three apps call the backend through one versioned HTTPS APIArchitectureOld app versions stay on phones for months, so the API cannot change in one release.
Modules call each other directly, not through a message brokerArchitectureIt decides how every module waits for, retries and fails with the others.
Only Payments talks to the external payment providerArchitectureIt binds every module, because only one may call the provider; switching providers later changes one module instead of several.
Write the backend in PythonArchitectureEvery module, library, build tool and new hire depends on it.
Dispatch picks the nearest available courierDetailed designIt lives inside Dispatch behind its interface, so changing it touches one module.
Add an index on the customer_id column of the orders tableDetailed designIt is added or dropped in minutes, and no query returns a different result.
Menu keeps each restaurant's dishes in memory for 60 secondsDetailed designIt is a setting inside Menu; other modules see the same interface either way.
Name the order total function calculate_totalDetailed designAll callers are in one codebase, so an editor's rename tool updates them in seconds.

Each architecture reason names many parts, a long cost of change, or something outside the code that must also change, such as live data or installed apps. Each detailed design reason names a single part or a change measured in seconds or minutes.

Detailed design is not less important than architecture. A poor Dispatch algorithm assigns couriers who are far from the restaurant, and customers notice late meals within a day. The difference is the cost of the repair. A better algorithm ships in the next backend release and changes one module, while a second database takes months.

A Detail That Grows into Architecture

The scope of a decision can grow after it is made. The team's first list of order status values is placed, accepted, ready, picked up and delivered. At first the list lives inside Orders and looks like detailed design.

A few months later, all three apps display these statuses, and Notifications sends a message on every change. Adding a status now touches five parts and needs an app release. The same decision has moved toward the architecture end of the spectrum, which is why the three questions are asked about the system after release.

Part 3: Why Architecture Decisions Matter

Architecture decisions constrain every decision made after them. The food-delivery app stores everything in one PostgreSQL database. A single transaction (a group of database writes that either all succeed or all roll back together) can therefore save an order and its payment record at once. Later decisions build on that fact. The order reports are written as SQL joins across several modules' tables, and Payments assumes the order row is saved in the same transaction.

Each later decision is reasonable on its own. Each one also adds to the cost of ever reversing the first. The other early decisions constrain later work in the same way: because all six modules ship as one deployable artifact, a fix to Notifications waits for a release of the whole backend.

The Dispatch Story

In the first version, Dispatch needs the list of orders that are ready for pickup. Orders offers a function for this, but it returns less than Dispatch needs. A direct SQL query on the orders and order_lines tables is quicker to write than a change to Orders, so Dispatch reads the Orders tables directly. Nobody treats this as an architecture decision, because it looks like a detail inside Dispatch. Over the next 18 months, the number of Dispatch queries that read Orders tables grows to 14.

The app then grows. At the dinner peak, 2,000 couriers are online, and each Courier app sends its location every 4 seconds. That is 500 location writes per second (2,000 divided by 4), and they go to the database that also saves new orders and answers Dispatch's matching queries. Placing an order slows from about 350 milliseconds to about 1,200 milliseconds at that peak (illustrative figures), because these writes and Dispatch's matching work compete with ordering for the same servers and database. The team plans to move Dispatch and the courier locations to a database of their own.

The early shortcut makes the move expensive. All 14 queries must be rewritten to call Orders, and Orders must add functions to answer them. Until all 14 queries are rewritten, the team that owns Orders cannot rename a column without breaking Dispatch. In this illustrative case, the team estimates the work at 3 engineers for about 7 weeks, and the two teams must release in a fixed order.

Suppose instead that the team had made one rule at the start: a module reads another module's data only through that module's interface. Dispatch would then hold no SQL that names an Orders table. The move would change only Dispatch's own storage code, taking 1 engineer about 1 week.

The diagram below compares the two versions of the move.

Moving Dispatch to Its Own Database
Moving Dispatch to Its Own Database

The rule about who may read which tables looked like detailed design, because one engineer made it inside one module. It was an architecture decision, because its scope was every Orders table and every team that changes them. The direct queries created coupling between Dispatch and the Orders tables. The Coupling, Cohesion, and Dependencies lesson in this chapter examines that kind of dependency in detail.

The story does not show that one shared database was a mistake. One database was a sound choice for a team of six. The mistake was making an architecture decision without recognizing it, so nobody weighed its cost before the code depended on it.

Part 4: Architecture Styles

An architecture style is a named, reusable set of architecture decisions with known trade-offs. Instead of making every architecture decision one at a time, a team can choose a style that fixes several of them together. The name of the style then tells every engineer which decisions were made.

Calling the food-delivery app's first version a monolith states four decisions at once. There is one deployable artifact, one process and one database, and modules call each other through functions. An engineer who knows the style also knows its trade-offs before reading any code. Releases are simple and one transaction can cover several modules, but every module is released and scaled together.

A style is different from a design pattern. A design pattern usually solves a problem inside one part, such as a repository class that reads and writes one kind of database record. Most design patterns therefore sit at the detailed design end of the spectrum. A style sits at the architecture end and shapes the whole system or a large part of it.

Real systems usually combine several styles, because each style answers a different question. One style decides how the system is deployed, another how the code inside one deployable is organized, and a third how the parts communicate. The food-delivery backend can be a monolith organized in layers, while Notifications reacts to order events (messages announcing that something happened, such as an order being placed).

The Fifteen Styles in This Course

The table below previews the fifteen styles of the Architecture Styles chapter by their core idea. Each lesson in that chapter teaches its style in full.

StyleCore idea
Monolith ArchitectureAll modules ship as one deployable artifact, run in one process and share one database.
Layered (N-Layer) ArchitectureCode is split into layers by technical job, and each layer calls only the layer directly below it.
N-Tier ArchitectureThe system is split into tiers, such as presentation, application and data, that run on their own machines and talk over the network.
Client-server ArchitectureClients send requests to a server, which owns the data and the rules and returns responses.
Microservices ArchitectureEach business area is an independently deployable service that owns its data.
Service-Oriented Architecture (SOA)Shared services are reused across an organization through formal contracts, often over a central bus.
Event-driven ArchitectureParts publish events about what happened, and other parts react without the publisher knowing who listens.
Pipe-and-Filter ArchitectureData passes through a chain of independent processing steps, each with one job.
Microkernel (Plugin) ArchitectureA small, stable core holds what every use needs, and plugins add variable features.
Hexagonal (Ports and Adapters)Business rules sit inside the application and reach databases, user interfaces and other outside systems only through interfaces they define.
Clean Architecture/Onion ArchitectureCode is arranged in rings, and source code dependencies point only inward.
Broker ArchitectureAn intermediary receives each request, finds a server that can handle it and forwards it.
Peer-to-Peer ArchitectureEvery participant, called a peer, both requests and supplies resources, and peers talk directly instead of through a central server.
Space-Based ArchitectureProcessing units keep working data in a replicated in-memory store and write it to the database later, outside the request path.
Serverless ArchitectureSmall functions run in response to events on a platform the cloud provider manages, which bills only for the compute they use.

Each style lesson answers the same questions. Which decisions does the style fix? Which quality attributes do those decisions improve, which do they cost, and when is the style the right choice?

Part 5: The Architect's Job

An architect is whoever makes the architecture decisions for a system. It is a role, not always a job title. In the six-engineer food-delivery team, the most senior engineer does it for a few hours a week. A company of 400 engineers may have several people who do it full time.

The role has four parts:

  1. Recognize architecture decisions. Apply the three questions to any decision that might touch many parts, especially one that looks like a detail.
  2. Make each decision by balancing quality attributes against constraints. The choice of one shared database favored fast development and low running cost, but it risked slow orders at peak load. It also fit the constraints: a team of six and a launch in four months.
  3. Record each decision. An architecture decision record (ADR) is a short document that records one architecture decision with its context, the decision itself and its consequences.
  4. Check that the code follows each decision. Code review and automated tests keep the code consistent with the records. One such test fails when a module's code names another module's tables.

Recording matters because the people who change a decision are often not the people who made it. The engineers who inherit Dispatch's 14 direct queries need a record, written on the day of the decision, that explains why they exist.

For the Dispatch story, the missing record would have been short. It would state that modules read each other's data only through interfaces, so any module can later move to its own database. It would also state the cost: Orders adds one function for each new question that Dispatch asks.

The rest of this chapter supplies the tools for this work. The Responsibilities and Boundaries lesson shows how to give each part one clear job. The Coupling, Cohesion, and Dependencies lesson shows how strongly parts depend on each other. The Quality Attributes lesson defines what a decision improves or harms and how to measure it. The Constraints and Trade-offs lesson compares options and fills in a complete architecture decision record.

FAQs

Q: Is software architecture only about high-level diagrams?

No. A diagram shows the parts and the paths between them, but architecture also includes the decisions behind that structure. The cost in the Dispatch story came from a decision that no diagram of the first version showed: which module may read which tables.

Q: Is choosing a programming language or a framework an architecture decision?

Usually, yes. Every module, library, build tool and new hire depends on the language, and changing it means rewriting most of the code. A library used inside one module, such as a date-parsing library in Orders, is detailed design, because replacing it touches one part.

Q: Does a small team need an architect?

A small team needs someone to make and record architecture decisions, even if nobody holds the title. Without that role, architecture decisions still get made, but as shortcuts, like the first Dispatch query.

Q: Should a new system start with one particular style?

No single style is always correct, because the right choice depends on the system's quality attributes and constraints. Many teams start with a monolith, as the food-delivery app did, because it is cheap for a small team to build and run. The Constraints and Trade-offs lesson in this chapter shows how to compare the options.

Key Takeaways

  • Software architecture is the structure of a system: its parts, their responsibilities, how they communicate, and the decisions about them that are expensive to change later.
  • Detailed design is the set of decisions inside one part, behind its interface, which can change in hours or days without affecting other parts.
  • Three questions separate the two: scope (how many parts and teams), cost of change (engineer time after release), and reversibility (whether a code change alone undoes it).
  • Decisions sit on a spectrum, and they can move along it. A detail inside one module becomes architecture once many parts depend on it, as the order status values did.
  • Architecture decisions constrain every later decision. In the Dispatch story, 14 direct queries turn a one-week database move into an estimated 7 weeks of work for 3 engineers.
  • An architecture style is a named, reusable set of architecture decisions with known trade-offs. Real systems usually combine several styles.
  • The architect recognizes, makes, records and checks architecture decisions, balancing quality attributes against constraints.

The next lesson, Responsibilities and Boundaries, starts with the first two elements of the definition. It shows how to give each part of the food-delivery app one clear job and where to draw the lines between the parts.

Reading Progress

0%


Vote for new content

On This Page

Know the Vocabulary

Part 1: A Working Definition of Software Architecture

The Food-Delivery App

Part 2: Architecture Versus Detailed Design

The Three-Question Test

Sorting Ten Decisions from the Food-Delivery App

A Detail That Grows into Architecture

Part 3: Why Architecture Decisions Matter

The Dispatch Story

Part 4: Architecture Styles

The Fifteen Styles in This Course

Part 5: The Architect's Job

FAQs

Key Takeaways