Why Software Architecture Matters
Software Architecture Fundamentals
Chapter 1 · Why Software Architecture Matters
This course is a direct sequel to Design Patterns — but it operates at a genuinely different scale. A pattern solves a local problem: how two or three objects collaborate. Architecture solves a system-shape problem: how an entire codebase is organized, and — the specific thing this chapter measures — how expensive a given decision is to change once the system has grown around it.
Architecture vs. Design vs. Implementation
These three terms get used loosely, but they describe genuinely different scopes of decision, distinguished by the same question at every level: how much of the codebase does changing your mind touch?
| Level | Example decision | Typical blast radius of changing it |
|---|---|---|
| Implementation | Using a for loop instead of a list comprehension inside one function | One function |
| Design (patterns) | Which discount Strategy object an Order currently holds | One line — verified below |
| Architecture | Whether business logic talks to storage directly, or through one boundary | Every function that touches storage — verified below |
A Local Decision, Verified Cheap to Change
Design Patterns Chapter 7 built exactly this kind of local decision: an Order holding a swappable shipping_strategy. That chapter verified something directly relevant here — swapping StandardShipping for ExpressShipping on an already-created Order object took one line (order.set_shipping_strategy(ExpressShipping())), and every other part of the codebase was completely unaffected. That's a design-level decision: cheap, local, reversible.
This chapter measures the opposite case — a decision made at the architecture level, where getting the boundary wrong makes an otherwise-simple change expensive.
An Architectural Decision, Measured
Without a Boundary: Storage Woven Directly Into Business Logic
open( (Python's own file-open call) finds it in 3 of 3 — every single function. There is no single place in this codebase that "does storage" — the decision to use flat text files is scattered across every function that happens to need persistence.
With a Boundary: One Repository, Everything Else Unchanged
open( now finds it in 0 of 3. All storage-specific code lives in exactly one place: FileOrderRepository.
InMemoryOrderRepository (same three methods, backed by a Python list instead of a file), and injecting it in place of FileOrderRepository: both repositories were captured via Python's own inspect.getsource() before and after the swap. The three business functions' own source text was confirmed character-for-character identical before and after — record_order, record_payment, and generate_report were never opened, let alone edited. Calling generate_report(file_repo) and generate_report(memory_repo) with the same recorded order both correctly returned ['ORD-1,100'].
Why This Gets Worse, Not Better, as the Codebase Grows
record_refund, generate_payment_report, generate_refund_report, each with their own direct open() call) produced 6 of 6 functions needing a touch to swap storage — the same 1:1 ratio as the original 3-function version. The boundary-with version's own cost stays flat at 0 business-function touches regardless of how many functions call the repository — only the one-time cost of writing a new repository class changes.
Repository) is one instance of a much more general idea this course returns to in every chapter: Layered Architecture (Chapter 2) and Hexagonal Architecture (Chapter 7) are both, at heart, systematic ways of making sure this kind of boundary exists before you need it, not after.
Where This Connects
| This chapter's finding | What it sets up |
|---|---|
| Design Patterns Chapter 7's one-line Strategy swap, reused directly as the "cheap" baseline | Every pattern in that course operates at this same low-cost, local scale — architecture is the layer above it, not a replacement for it |
| The Repository boundary keeping business logic at 0 touches | Chapter 2's Layered Architecture generalizes this one boundary into a full, named set of layers |
| An unexamined decision (no boundary) costing 1 touch per function, forever | Chapter 9's Architectural Decision Records exist specifically to make a decision like "do we need a boundary here" deliberate and recorded, not accidental |
Hands-On Exercises
Add a fourth boundary-free function, record_refund(order_id, amount), to this chapter's own first (no-repository) example, using the same direct open() pattern. Verify the touch count is now 4 of 4.
Add a matching save_refund(order_id, amount) method to this chapter's own FileOrderRepository and InMemoryOrderRepository, plus a new business function record_refund(repo, order_id, amount) that calls it. Verify this new function's source contains no open( call, and that it works correctly against both repositories.
Using this chapter's own compare-table (Implementation / Design / Architecture), classify each of the following as one of the three levels, and justify your answer using this chapter's own "how much of the codebase does changing your mind touch?" test: (a) renaming a local variable inside one function, (b) switching an Order's discount strategy at runtime, (c) deciding whether a system is one monolith or split into several services.
Chapter 1 Quick Reference
- The test: a decision's level (implementation / design / architecture) is measured by how much of the codebase changing your mind touches — not by how important it sounds
- Verified: a design-level decision (Design Patterns' own Strategy swap) cost 1 line; an architecture-level decision made without a boundary cost 3 of 3 (then 6 of 6) function touches; the identical decision made with one boundary (a Repository) cost 0 business-function touches, confirmed via character-identical source text before and after the swap
- Next chapter: Layered Architecture — generalizing this one boundary into a full, named set of layers