Home / Notebooks / Software Design
Software Design
intermediate

Fundamentals of Software Architecture

Core concepts from Mark Richards and Neal Ford's comprehensive guide to software architecture thinking and styles

April 28, 2026
Updated regularly

Fundamentals of Software Architecture

Richards and Ford open with two laws that frame the entire book:

> First Law: Everything in software architecture is a trade-off. > > Second Law: Why is more important than how.

An architect's primary job is not to choose technologies — it is to analyze trade-offs and make decisions that align with business goals.

---

What Architects Do

Architecture is broader than code. An architect must:

  • Define architecture characteristics (the "-ilities")
  • Select architecture patterns and styles
  • Make structural decisions about component boundaries and coupling
  • Analyze trade-offs and document decisions with rationale
  • Guide development teams and maintain technical vision
  • The four dimensions of software architecture:

    DimensionConcerns
    StructureArchitecture style (microservices, layered, event-driven…)
    CharacteristicsAvailability, scalability, security, agility…
    DecisionsRules and constraints the system must follow
    Design principlesGuidelines (not hard rules) for implementation
    ---

    Architecture Characteristics

    Also called "non-functional requirements" — but Richards and Ford reject that term. These are first-class architectural concerns, not afterthoughts.

    Categories

    Operational characteristics — runtime behavior:

  • Availability, reliability, recoverability
  • Performance, scalability, elasticity
  • Deployability
  • Structural characteristics — internal quality:

  • Modularity, maintainability, testability
  • Configurability, extensibility
  • Cross-cutting characteristics — apply everywhere:

  • Security, accessibility, legality
  • Observability, usability
  • The Least Worst Architecture

    No architecture satisfies all characteristics simultaneously. Optimizing for one degrades others.

    Scalability ←→ Simplicity       (more nodes = more complexity)
    Security    ←→ Performance      (encryption, validation add latency)
    Agility     ←→ Reliability      (fast change = higher failure risk)
    Deployability ←→ Testability    (more deployments = more test surface)
    

    > Identify the 3–7 characteristics that matter most for this system. Ignore the rest — designing for everything designs for nothing.

    ---

    Modularity

    The degree to which components of a system can be separated, recombined, and understood independently.

    Three metrics to measure modularity:

    Cohesion

    How related are the elements inside a module?

    High cohesion: OrderService handles only order operations
    Low cohesion:  UtilService handles orders, emails, logging, PDF generation
    

    Ideal: functionally cohesive — all code in a module serves one purpose.

    Coupling

    How dependent are modules on each other?

  • Afferent (incoming) coupling — how many modules depend on this one
  • Efferent (outgoing) coupling — how many modules this one depends on
  • Instability = efferent / (afferent + efferent) — 0 is stable, 1 is unstable
  • A module with high afferent coupling is risky to change (many callers break)
    A module with high efferent coupling is fragile (breaks if any dependency changes)
    

    Connascence

    A more precise measure of coupling — two components are connascent if a change in one requires a change in the other.

    TypeExampleStrength
    NameMethod name used by callerWeak
    TypeParameter type must matchWeak
    MeaningMagic number (0 = inactive)Medium
    PositionArgument order must matchMedium
    AlgorithmBoth sides must use same encodingStrong
    Execution orderMust call A before BStrong
    Prefer weak connascence (name, type) and minimize strong connascence (algorithm, execution order) across module boundaries.

    ---

    Architecture Decisions

    Architecture decisions define the constraints that structure how the system is built. They must be documented with rationale — not just the what, but the why.

    Architecture Decision Records (ADRs)

    A lightweight format to record decisions:

    ## ADR-001: Use PostgreSQL as primary data store
    
    **Status:** Accepted
    
    **Context:**
    We need a relational store with ACID guarantees for financial transactions.
    Team has existing PostgreSQL expertise.
    
    **Decision:**
    Use PostgreSQL as the primary data store.
    
    **Consequences:**
    - Strong consistency for transaction data
    - Vertical scaling limits require sharding strategy at >10M records
    - MySQL expertise from previous system is not transferable
    

    Anti-Patterns in Architecture Decisions

    Covering Your Assets — avoiding decisions to avoid blame. Architecture requires commitment.

    Groundhog Day — re-litigating the same decisions repeatedly. ADRs prevent this.

    Email-Driven Architecture — critical decisions buried in email threads. Use ADRs in version control instead.

    ---

    Fitness Functions

    Automated mechanisms to verify that the architecture remains consistent with its intended characteristics over time.

    Architecture characteristic → Fitness function → Pass / Fail
    
    Availability    → uptime monitor, chaos injection tests
    Modularity      → ArchUnit test: no package cycles
    Performance     → load test: p99 latency < 200ms
    Security        → OWASP dependency scan in CI pipeline
    

    Types:

  • Atomic — test a single characteristic in isolation (unit/integration test)
  • Holistic — test multiple characteristics together (end-to-end, load test)
  • Triggered — run on demand or on schedule
  • Continual — run constantly in production (synthetic monitoring)
  • > Fitness functions prevent architecture drift — the gap between the intended and actual architecture.

    ---

    Component-Based Thinking

    Architects think in components (deployable or logical units), not classes.

    Component Identification Process

  • Identify initial components from domain or technical partitioning
  • Assign user stories or domain behaviors to components
  • Analyze coupling and cohesion — refactor component boundaries
  • Repeat with developer feedback
  • Partitioning Styles

    Technical partitioning — organize by technical layer.

    presentation/
      ├── UserController.java
    service/
      ├── UserService.java
    repository/
      ├── UserRepository.java
    

    Cohesion is low across features — a "user" change touches all layers.

    Domain partitioning — organize by business domain (DDD-inspired).

    orders/
      ├── OrderController.java
      ├── OrderService.java
      ├── OrderRepository.java
    users/
      ├── UserController.java
      ├── UserService.java
    

    High cohesion per feature, lower coupling across domains. Preferred for microservices.

    ---

    Architecture Styles

    Layered (n-Tier)

    The most common style. Separates concerns into horizontal layers.

    Presentation Layer   (UI, controllers)
        ↓
    Business Layer       (services, use cases)
        ↓
    Persistence Layer    (repositories, ORM)
        ↓
    Database Layer
    
    CharacteristicRating
    SimplicityHigh
    DeployabilityLow (deploy entire app)
    ScalabilityLow (scale entire app)
    TestabilityMedium
    ElasticityLow
    Best for: small teams, internal tools, low-traffic systems.

    Pipeline

    Data flows through a sequence of independent processing steps (pipes and filters).

    Input → Filter A → Filter B → Filter C → Output
               ↑           ↑           ↑
          (transform)  (validate)  (enrich)
    

    Used by: Unix shell pipes, ETL pipelines, build tools, compilers.

    CharacteristicRating
    ModularityHigh
    ScalabilityMedium
    SimplicityMedium
    DeployabilityLow (usually one unit)

    Microkernel (Plugin)

    A stable core system with pluggable feature modules.

    Core System (minimal, stable)
      ├── Plugin: PDF Export
      ├── Plugin: CSV Export
      ├── Plugin: Email Notification
      └── Plugin: Slack Notification
    

    Used by: IDEs (VS Code, IntelliJ), browsers (extensions), CMS platforms.

    CharacteristicRating
    ExtensibilityHigh
    TestabilityHigh
    DeployabilityMedium
    ScalabilityLow

    Service-Based

    A pragmatic distributed style: few coarse-grained services (4–12), all sharing a single database.

    API Gateway
      ├── OrderService       ┐
      ├── UserService        ├── shared database
      ├── PaymentService     ┘
      └── NotificationService → own DB
    

    Fewer operational concerns than microservices, more flexibility than monolith.

    CharacteristicRating
    AgilityHigh
    DeployabilityHigh
    ScalabilityMedium
    Fault toleranceMedium
    SimplicityMedium

    Event-Driven

    Components communicate asynchronously via events. Two topologies:

    Broker topology — events flow through a message broker, no central coordinator.

    OrderPlaced event →  broker
        ├── InventoryService (consumes)
        ├── BillingService (consumes) → PaymentProcessed event → broker
        └── NotificationService (consumes)
    

    Mediator topology — a central orchestrator coordinates the workflow.

    OrderPlaced event → Mediator
        → calls InventoryService
        → calls BillingService
        → calls NotificationService
    
    CharacteristicRating
    ScalabilityHigh
    Fault toleranceHigh
    AgilityHigh
    SimplicityLow
    TestabilityLow

    Space-Based

    Removes the database as the bottleneck. Uses in-memory data grids with replicated state across processing units.

    Processing Unit 1 (in-memory cache + app logic)
    Processing Unit 2 (in-memory cache + app logic)
    Processing Unit N (in-memory cache + app logic)
            ↓
    Virtualized middleware (routing, messaging, data replication)
            ↓
    Database (async persistence — not in the hot path)
    

    Used for: extreme high-concurrency systems (ticket sales, auction sites).

    CharacteristicRating
    ScalabilityVery High
    ElasticityVery High
    PerformanceVery High
    SimplicityVery Low
    CostVery High

    Microservices

    Each service is independently deployable, owns its own data store, and communicates over network calls.

    API Gateway
      ├── UserService     → users DB
      ├── OrderService    → orders DB
      ├── PaymentService  → payments DB
      └── SearchService   → search index
    

    Bounded context (from DDD): each service models one domain concept and is the single source of truth for it.

    Sidecar pattern: cross-cutting concerns (logging, tracing, auth) extracted into a sidecar process — not embedded in business code.

    Service Pod:
      ├── Business Service
      └── Sidecar (logging, metrics, mTLS)
    
    CharacteristicRating
    ScalabilityHigh
    AgilityHigh
    DeployabilityHigh
    Fault toleranceHigh
    SimplicityVery Low
    Data consistencyLow (eventual)
    CostHigh
    ---

    Choosing an Architecture Style

    There is no universally correct style. Choose based on the system's dominant characteristics.

    Small team, CRUD app, time pressure     → Layered
    Plugin-extensible product               → Microkernel
    ETL, data processing pipeline           → Pipeline
    Moderate distribution, shared DB ok     → Service-based
    Async workflows, decoupled producers    → Event-driven
    Extreme throughput, in-memory scale     → Space-based
    Independent teams, deployability first  → Microservices
    

    Questions to ask before choosing:

  • What are the top 3 architecture characteristics this system must optimize for?
  • How large and distributed is the team?
  • What is the expected rate of change?
  • What is the operational maturity (can we run Kubernetes, manage distributed tracing)?
  • ---

    Architecture Quantum

    The smallest deployable unit with high functional cohesion and high architectural coupling.

  • A monolith has one quantum
  • Microservices have many quanta (one per service)
  • Architecture quanta define the boundaries where different architecture characteristics can apply independently.

    Order domain: high availability (active-active)
    Report domain: low cost (single node, batch)
    
    → Different quanta → different deployment and scaling strategies
    

    ---

    Diagramming Architecture

    C4 Model (Context, Container, Component, Code)

    Level 1 — System Context: your system + external users and systems
    Level 2 — Container: applications, databases, queues inside your system
    Level 3 — Component: services and modules inside a container
    Level 4 — Code: classes, functions (usually auto-generated from IDE)
    

    Use Level 1 and 2 for stakeholder communication. Level 3 for developer onboarding.

    Rules for Architecture Diagrams

  • Every box needs a label that explains what it is and what it does
  • Every arrow needs a label that explains what flows and the protocol/mechanism
  • Include a title and a legend
  • Avoid using UML notation unless your audience knows it
  • ---

    Key Takeaways

    PrincipleRule
    Trade-offs are the jobThere is no best architecture — only best for a context
    Why over howRecord the rationale, not just the decision
    Characteristics firstIdentify the -ilities before picking a style
    Fitness functionsAutomate architectural governance or drift is inevitable
    ModularityMeasure cohesion and coupling — don't guess
    Domain partitioningPrefer domain over technical layers for distributed systems
    QuantaDifferent parts of the system can have different architectures
    ADRsEvery significant decision deserves a record
    ---

    Resources

  • Fundamentals of Software Architecture — Richards & Ford
  • Software Architecture Patterns — Mark Richards
  • Building Evolutionary Architectures — Ford, Parsons, Kua
  • C4 Model for Architecture Diagrams
  • Topics

    Software ArchitectureArchitecture PatternsSoftware DesignEngineering

    Found This Helpful?

    If you have questions or suggestions for improving these notes, I'd love to hear from you.