Chapter 2: Discerning Coupling in Software Architecture
saht coupling architecture-quantum static-coupling dynamic-coupling distributed-systems
Status: Notes complete
Overview
Chapter 2 provides the foundational vocabulary for the rest of the book. Before you can reason about how to split a system, you must be able to reason about how its parts are connected — and with what kind of connection. The chapter introduces coupling not as a binary (coupled vs. decoupled) but as a multi-dimensional property with distinct types, each carrying different implications for architecture.
The central conceptual innovation is the architecture quantum: a rigorously defined unit of architecture that serves as the smallest independently deployable unit with high functional cohesion. Unlike a service or a component, a quantum captures not just code boundaries but also data ownership and the forces that bind pieces together. This unit of analysis recurs throughout the book as the primary lens for evaluating decomposition decisions.
The chapter also introduces a three-axis model of dynamic coupling — the coupling that emerges at runtime when components communicate. By separating communication style (synchronous vs. asynchronous), consistency model (atomic vs. eventual), and coordination style (orchestrated vs. choreographed), the authors create an eight-combination space that exhausts the major architectural options for inter-service communication. Understanding where a system sits in this space — and why — is the prerequisite for all the decomposition and restructuring discussions in later chapters.
Core Concepts
Coupling: A measure of how dependent one component is on another. Coupling is not binary — it varies in type, strength, and direction. High coupling makes components harder to change independently; low coupling increases modularity and independent deployability.
Architecture quantum: The smallest unit of architecture that has high functional cohesion and is independently deployable. A quantum has its own high static coupling (it includes everything it needs to function), high functional cohesion (its parts work together toward a unified purpose), and independent deployability (it can be deployed without deploying other quanta).
Static coupling: The coupling that exists at build/compile time — the structural dependencies between components (imports, libraries, shared schemas). It is visible in the dependency graph.
Dynamic coupling: The coupling that exists at runtime — how components communicate, what consistency they require, and how they coordinate. It is not visible in the static dependency graph.
Afferent coupling: The number of classes/components that depend on a given component. High afferent coupling means many things depend on you — you are important and hard to change.
Efferent coupling: The number of classes/components that a given component depends on. High efferent coupling means you depend on many things — you are fragile, because any of your dependencies can break you.
Connascence: A refinement of coupling vocabulary. Two components are connascent if changing one requires changing the other. Can be static (connascence of name, type, meaning, algorithm) or dynamic (connascence of execution, timing, value, identity).
Architecture Quanta
Definition
The architecture quantum is one of the most important original contributions of the book. It refines the notion of a “service” or “component” into something more precise and analytically useful.
An architecture quantum is:
┌─────────────────────────────────────────┐
│ High Functional Cohesion │
│ (parts work together for one purpose) │
│ │
│ + Independently Deployable │
│ (can deploy without other quanta) │
│ │
│ + High Static Coupling │
│ (includes all it needs to function) │
└─────────────────────────────────────────┘
Importantly, high static coupling is a feature, not a bug within a quantum. A quantum is supposed to contain everything it needs — tight internal coupling is what makes it independently deployable. The problem is coupling between quanta, not within them.
Quanta in Different Architecture Styles
| Architecture Style | Typical Number of Quanta |
|---|---|
| Monolith | 1 |
| Modular monolith | 1 (unless UI is separate) |
| Microservices (fine-grained) | Many (1 per service ideally) |
| Service-based architecture | Several (1 per coarse-grained service) |
| Event-driven architecture | Varies — depends on consumer/producer boundaries |
Why Quanta Matter
The quantum is the unit at which you can make independent deployment decisions. If two services must always be deployed together (because one’s API change requires the other to update simultaneously), they are effectively one quantum even if they run in separate processes.
The quantum concept explains why microservices are not automatically independent: if Service A calls Service B synchronously and depends on B’s response to complete its own operation, A and B are dynamically coupled even if they are statically separate. Under failure conditions, they behave as a single unit.
Single-Quantum vs. Multi-Quantum Architectures
Single Quantum (Monolith):
┌──────────────────────────┐
│ UI + App + DB │ ← All one deployable unit
└──────────────────────────┘
Benefit: Simple transactions, simple deployment
Cost: No independent scaling, team coupling
Multi-Quantum (Microservices):
┌────────┐ ┌────────┐ ┌────────┐
│ Q1 │ │ Q2 │ │ Q3 │
│ Order │ │Payment │ │Shipping│
└────────┘ └────────┘ └────────┘
Benefit: Independent deployability and scaling
Cost: Distributed systems complexity
Static Coupling
Afferent and Efferent Coupling
Afferent coupling (Ca) — incoming dependencies — tells you how important a component is. If many things depend on you, you are central; changing you requires changing many other things.
Efferent coupling (Ce) — outgoing dependencies — tells you how fragile a component is. If you depend on many things, any of those things failing or changing can break you.
Instability metric: I = Ce / (Ca + Ce)
I = 0 → Maximally stable (many dependents, no dependencies)
I = 1 → Maximally unstable (many dependencies, no dependents)
Desired direction of coupling:
Stable ←── Unstable
(unstable components should depend on stable ones,
not the other way around)
Types of Static Coupling in Distributed Systems
| Coupling Type | Description | Example |
|---|---|---|
| Shared library | Services share a compiled library | Common data transfer objects (DTOs) |
| Shared database | Services read/write the same database/schema | Legacy monolith pattern |
| Shared schema | Services consume the same message schema | Event-driven systems |
| API contract | Service A depends on Service B’s interface | REST/gRPC calls |
Shared database is the most dangerous form of static coupling in distributed architectures: it makes it impossible to evolve a service’s data model independently, and it couples deployment — a schema change in one service’s tables can break another service that shares the database.
Coupling and Independent Deployability
The relationship between static coupling and independent deployability is direct:
More static coupling between quanta
→ More coordination required for deployment
→ Less independent deployability
→ Larger effective deployment unit
→ Higher risk per deployment
→ Less frequent deployment
→ Slower feedback loops
This cascade is why the authors treat reducing inappropriate static coupling between quanta as one of the highest-priority concerns in distributed architecture design.
Dynamic Coupling
Static coupling is visible in code and dependency graphs. Dynamic coupling is the coupling that emerges at runtime — it cannot be read from the source code alone. It is determined by three orthogonal dimensions:
The Three Dimensions of Dynamic Coupling
COMMUNICATION CONSISTENCY COORDINATION
───────────── ─────────── ────────────
Synchronous Atomic Orchestrated
vs. vs. vs.
Asynchronous Eventual Choreographed
Each dimension is an independent binary choice, producing 2 × 2 × 2 = 8 possible combinations of dynamic coupling style.
Dimension 1: Communication — Synchronous vs. Asynchronous
Synchronous communication: The caller waits for a response before proceeding. The caller is blocked during the call.
Caller ──── request ────► Service
Caller ◄─── response ─── Service
(caller blocked during round trip)
Asynchronous communication: The caller sends a message and continues. The response (if any) arrives later via callback, event, or polling.
Caller ──── message ────► Queue/Broker
Caller continues executing...
Queue/Broker ──► Service
Service ──── response ──► Queue/Broker (optional)
Caller ◄──── response ─── Queue/Broker (optional)
| Synchronous | Asynchronous | |
|---|---|---|
| Coupling strength | High — caller depends on callee availability | Low — decoupled by queue |
| Latency | Additive — caller waits for each hop | Non-additive for caller |
| Error handling | Immediate — caller gets error | Complex — need dead-letter queues, idempotency |
| Throughput | Limited by slowest service | Caller unblocked; services process at own rate |
| Data freshness | Immediate response | Potentially stale |
| Debugging | Easier — clear request/response trail | Harder — distributed trace spans multiple systems |
Dimension 2: Consistency — Atomic vs. Eventual
Atomic consistency: All operations in a transaction either all succeed or all fail together. At any point, all consumers see the same state.
Eventual consistency: Updates propagate asynchronously; at any given moment different parts of the system may have different views of the data. The system will converge to consistency eventually, but there is a window of inconsistency.
Atomic consistency:
Write → All reads immediately see new value
[Requires distributed coordination — expensive]
Eventual consistency:
Write → Reads may see old or new value
for an indeterminate period
→ Eventually all reads see new value
[No coordination required — cheaper, more available]
| Atomic | Eventual | |
|---|---|---|
| Correctness | Strong — all nodes agree at all times | Weak — temporary divergence allowed |
| Availability | Lower — requires coordination (locks, 2PC) | Higher — no coordination required |
| Performance | Lower — coordination overhead | Higher — no blocking |
| Complexity | Lower for developers (familiar ACID model) | Higher — must handle conflict resolution, idempotency |
| Failure handling | Simpler — rollback | Complex — compensating transactions |
Dimension 3: Coordination — Orchestrated vs. Choreographed
Orchestrated coordination: A central orchestrator (often called a saga orchestrator or workflow engine) directs all participants, tells each one what to do and when, and handles failures centrally.
Orchestrated:
┌──────────────┐
│ Orchestrator │
└──────────────┘
/ | \
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌───────┐
│ Svc A │ │ Svc B │ │ Svc C │
└───────┘ └───────┘ └───────┘
(Orchestrator knows the workflow)
Choreographed coordination: Each service knows what to do based on events it receives. No central coordinator exists; behavior emerges from the reactions of individual services.
Choreographed:
┌───────┐ ──event──► ┌───────┐ ──event──► ┌───────┐
│ Svc A │ │ Svc B │ │ Svc C │
└───────┘ └───────┘ └───────┘
(Each service reacts to events; no central control)
| Orchestrated | Choreographed | |
|---|---|---|
| Coupling | Services coupled to orchestrator | Services coupled to event schema |
| Workflow visibility | High — orchestrator knows the whole workflow | Low — workflow is implicit in event chains |
| Error handling | Centralized — orchestrator manages compensation | Distributed — each service must handle its own failures |
| Flexibility | Lower — workflow changes require orchestrator updates | Higher — services can be added/removed without changing others |
| Complexity | Simpler for complex workflows | Can become chaotic (“choreography hell”) |
| Testability | Easier — test the orchestrator | Harder — must simulate full event chains |
The 8 Combinations
All eight combinations of (Sync/Async) × (Atomic/Eventual) × (Orchestrated/Choreographed) are possible, though some are more common and more coherent than others:
# Comm Consistency Coordination Typical Use Case
─ ──────── ─────────── ───────────── ─────────────────────────────────────
1 Sync Atomic Orchestrated Traditional SOA, distributed monolith
2 Sync Atomic Choreographed Rare — sync choreography is unusual
3 Sync Eventual Orchestrated Orchestrator reads stale data
4 Sync Eventual Choreographed Rare — inconsistent coordination
5 Async Atomic Orchestrated Saga pattern with orchestrator (most common in microservices)
6 Async Atomic Choreographed Saga pattern with choreography (events + compensation)
7 Async Eventual Orchestrated Event-driven with central saga manager, eventual reads
8 Async Eventual Choreographed Pure event-driven architecture (e.g., event sourcing + CQRS)
The most common production patterns:
- Combination 1 (Sync/Atomic/Orchestrated): Easy to reason about but creates strong temporal coupling; the “distributed monolith” anti-pattern often emerges here.
- Combination 5 (Async/Atomic/Orchestrated): The saga orchestrator pattern — the most common approach to distributed transactions in microservices.
- Combination 8 (Async/Eventual/Choreographed): Pure event-driven — maximum decoupling, maximum operational complexity.
Coupling and the CAP Theorem
The dynamic coupling choices map directly onto the CAP theorem’s constraints:
- Atomic consistency requires distributed coordination — it sacrifices availability or partition tolerance depending on how it is implemented.
- Eventual consistency allows availability and partition tolerance at the cost of consistency (by CAP’s definition).
- Synchronous communication creates a hidden form of availability coupling: if Service B is unavailable, Service A cannot complete its operation.
- Asynchronous communication breaks this availability coupling at the cost of increased latency and complexity.
Architects must understand that choosing atomic consistency in a distributed system is not just a correctness decision — it is an availability and performance decision with system-wide implications.
Connascence in Distributed Systems
Connascence (a vocabulary from the 1990s, revived in SAHT) provides a more nuanced vocabulary for coupling than simple “high/low”:
Static Connascence (in source code)
- Connascence of Name (CoN): Multiple components agree on a name (method name, field name). Weakest form.
- Connascence of Type (CoT): Components agree on a type.
- Connascence of Meaning (CoM): Components agree on the meaning of a value (e.g.,
1 = active,0 = inactive). - Connascence of Algorithm (CoA): Components must implement the same algorithm (e.g., same hashing function).
Dynamic Connascence (at runtime)
- Connascence of Execution (CoE): Order of execution matters between components.
- Connascence of Timing (CoT): Timing of execution matters.
- Connascence of Values (CoV): Multiple values must change together (e.g., related fields in different services).
- Connascence of Identity (CoI): Multiple components must reference the same entity instance.
In distributed systems, connascence of values and connascence of identity are the most dangerous forms — they require cross-service coordination that is expensive and failure-prone.
Trade-off Summary
Static Coupling Trade-offs
| Coupling Type | Low Coupling (good) | High Coupling (bad) |
|---|---|---|
| Between quanta | Independent deployability, evolutionary | Coordination overhead, deployment coupling |
| Within quantum | High cohesion, fewer network calls | — (desired, within limits) |
| Shared DB | — | Schema evolution coupling, deployment coupling |
| Shared library | Code reuse | Version coupling, ripple effects |
Dynamic Coupling Trade-offs
| Style | Benefits | Costs |
|---|---|---|
| Synchronous | Simple, immediate consistency | Temporal coupling, cascading failures |
| Asynchronous | Decoupled, resilient | Complex error handling, eventual consistency |
| Atomic | Strong correctness guarantees | Performance cost, coordination overhead |
| Eventual | High availability, high performance | Developer complexity, conflict resolution |
| Orchestrated | Visible workflow, central error handling | Orchestrator is a single point of failure/change |
| Choreographed | Flexible, decoupled | Invisible workflow, distributed error handling |
Decision Framework
Choose synchronous communication when:
- The caller genuinely needs the result before proceeding
- Latency budget allows for blocking
- The callee is highly available and fast
Choose asynchronous communication when:
- The caller does not need an immediate result
- Resilience to callee unavailability is important
- Throughput matters more than individual request latency
Choose atomic consistency when:
- Operations must be all-or-nothing (financial transactions, inventory)
- Data correctness is non-negotiable
- The performance/availability cost is acceptable
Choose eventual consistency when:
- Temporary inconsistency is tolerable
- High availability is more important than strong consistency
- The system operates at a scale where coordination is prohibitively expensive
Choose orchestration when:
- The workflow is complex and must be visible
- Centralized error handling and compensation is required
- Workflow changes are infrequent
Choose choreography when:
- Services must be maximally decoupled
- The system needs to support adding/removing services without changing others
- Workflows are relatively simple
Identify quantum boundaries by asking:
- Can this component be deployed without deploying these other components?
- Does this component share a database with these other components?
- Does this component fail or succeed as a unit with these other components?
If any answer is “no,” the components may be in the same quantum.
Sysops Squad Saga
Chapter 2 applies the quantum analysis to the Sysops Squad system to demonstrate what coupling looks like in practice.
Current State: One Quantum
The existing Sysops Squad monolith is a single quantum: all functionality (ticket creation, routing, dispatch, billing, reporting, knowledge base) is deployed together, shares a single database, and fails or succeeds as a unit.
┌─────────────────────────────────────────────────────────────┐
│ Sysops Squad Monolith (Single Quantum) │
│ │
│ [Ticket] [Routing] [Dispatch] [Billing] [Reporting] │
│ │
│ ────────────────── Shared Database ────────────────────── │
└─────────────────────────────────────────────────────────────┘
Static coupling: All modules share the database schema. A schema change for billing tables can break ticket routing queries.
Dynamic coupling: All operations are in-process function calls — synchronous, atomic (ACID transactions), and orchestrated by the application’s call stack.
Target State Analysis: Multiple Quanta
If the team moves to a distributed architecture, they must identify quantum boundaries. The chapter suggests the Sysops Squad system likely has several natural quanta:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Ticket Q │ │ Expert Q │ │ Billing Q │
│ │ │ │ │ │
│ Ticket DB │ │ Expert DB │ │ Billing DB │
└──────────────┘ └──────────────┘ └──────────────┘
↑ ↑ ↑
Ticket ops Routing/Dispatch Billing/Contracts
The Key Architectural Questions
The Sysops Squad saga in Chapter 2 establishes questions that later chapters will answer:
- What are the quantum boundaries? Which functionality belongs in each quantum?
- What is the static coupling between quanta? Do they share data? Share libraries?
- What is the dynamic coupling style? When Ticket creates a ticket and needs Expert to be notified, is that synchronous or asynchronous? Atomic or eventual?
- What consistency is required? If a ticket is created but routing fails, must the ticket creation also fail (atomic)? Or can the ticket exist and routing retry (eventual)?
These questions are concrete instances of the Chapter 2 framework applied to a real (fictional) system.
Key Takeaways
- Coupling is multi-dimensional — it exists in static form (build-time dependencies) and dynamic form (runtime communication patterns), and each type has distinct implications for architecture.
- The architecture quantum — defined by high functional cohesion, independent deployability, and high static coupling within — is the fundamental unit of analysis for distributed architecture decisions.
- Static coupling between quanta (shared databases, shared libraries, API contracts) reduces independent deployability and creates deployment coordination overhead.
- Afferent coupling (incoming) indicates importance and stability; efferent coupling (outgoing) indicates fragility. Well-designed systems have unstable components depending on stable ones, not the reverse.
- Dynamic coupling has three independent dimensions: communication style (sync/async), consistency model (atomic/eventual), and coordination style (orchestrated/choreographed), yielding 8 possible combinations.
- Synchronous communication creates temporal coupling — the caller is unavailable during the call and fails if the callee fails. Asynchronous communication breaks this coupling at the cost of complexity.
- Atomic consistency requires distributed coordination (expensive, reduces availability); eventual consistency improves availability and performance at the cost of developer complexity and temporary data divergence.
- Orchestration centralizes workflow visibility and error handling but creates a single point of change; choreography maximizes decoupling but makes workflows implicit and harder to debug.
- A system can be statically decoupled (separate services, separate databases) but dynamically highly coupled (synchronous calls, atomic transactions) — the static and dynamic dimensions must both be evaluated.
- The Sysops Squad monolith is a single quantum; migrating to distributed architecture requires identifying natural quantum boundaries, which in turn requires understanding the coupling between business domains.
Related Resources
- ch01-no-best-practices — Establishes the trade-off framing that this chapter operationalizes through coupling vocabulary
- ch03-decomposition — Applies quantum and coupling analysis to the decomposition decision
- DDIA Chapter 9 — Consistency and Consensus (the CAP theorem and distributed transactions in depth)
- DDIA Chapter 11 — Stream Processing (asynchronous, event-driven patterns)
Last Updated: 2026-05-30