1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
# Principle of Least Knowledge / Law of Demeter
> "Only talk to your immediate friends." — Ian Holland, Northeastern University, 1987
## Core Idea
The Law of Demeter states that a method may only invoke methods on itself, its parameters, objects it creates, and its instance variables — never on objects returned by other method calls. At architecture scale, LoD constrains communication patterns: a component should only interact with its immediate collaborators and never reach through them to access distant internal details. Robert C. Martin devotes several pages of Clean Code Chapter 6 to "Train Wrecks," "Hybrids," and "Hiding Structure." Martin Fowler pragmatically calls it the "Occasionally Useful Suggestion of Demeter." The key insight: LoD is a coupling-control rule that limits what components need to know about each other's internal structure.
## Violation Patterns
### 1. Train Wreck / Deep Object Navigation
**Heuristic:** Chained method/property calls that traverse multiple objects, exposing deep structural knowledge.
**Look for:**
- `ctxt.getOptions().getScratchDir().getAbsolutePath()`
- `order.getCustomer().getAddress().getCity()`
- Chains longer than two dots on objects (not data structures)
- Disguising chains with intermediate variables doesn't fix it — the caller still knows the internal structure
**Refactoring/Remedy:** Apply "Tell, Don't Ask" — instead of querying an object's internals, tell the object what you need done. `account.withdraw(amount)` replaces interrogating the balance externally. Create intention-revealing methods that hide the navigation.
### 2. Service Reach-Through / Transitive Dependencies
**Heuristic:** Service A calls Service B, which calls Service C, giving A implicit knowledge of the B→C relationship. If C changes its API, both B and A may break.
**Look for:**
- Distributed traces showing call chains longer than one hop from a given service's perspective
- A client service calling a downstream service then reaching through its DTOs to call another service
- Multi-hop synchronous flows that tightly couple components
**Refactoring/Remedy:** Use facade patterns and API gateways that aggregate calls so clients don't chain through services. Enforce that each layer calls only the layer directly below it.
### 3. Schema Reach-Through in Service Architectures
**Heuristic:** A service reads another service's internal tables, internal event payloads, or database entities directly — coupling itself to the other service's internal representation.
**Look for:**
- Services reading each other's database tables
- Integration tests that manipulate internal tables or queues of a service instead of using its public interface
- Azure explicitly calls "using database entities as events" an antipattern because it exposes internal details
**Refactoring/Remedy:** Enforce "database per service." Services communicate only through public APIs or published events. Event payloads should represent domain concepts, not internal database entities.
### 4. Client-Side Business Logic / Orchestrator Overreach
**Heuristic:** UI clients, API gateways, or orchestrator services replicate domain rules that should live behind service boundaries, effectively knowing too much about the internal logic of services.
**Look for:**
- Gateway/orchestrator code that queries multiple contexts and recomputes internal state
- Calling code that depends on deep internal fields of complex DTOs that should be encapsulated
- Clients assembling a domain operation by pulling many internal fields because no coherent "tell" operation exists
**Refactoring/Remedy:** Expose narrow, intention-revealing APIs (PlaceOrder, ReserveInventory) instead of leaking raw data for callers to manipulate. Push domain logic into the service that owns the bounded context.
### 5. Layer Skipping
**Heuristic:** Presentation layer directly calls data access, bypassing business logic. Lower layers reach up to higher layers.
**Look for:**
- UI code directly executing SQL or calling repository methods
- Infrastructure modules importing from service/domain modules
- Circular imports or lazy imports used to work around circular dependencies
**Refactoring/Remedy:** Enforce strict layer dependency direction. Higher layers call lower layers only through defined interfaces. Use dependency inversion to break circular dependencies.
## System-Scale Notes
- Robert C. Martin draws an important distinction: data structures (DTOs, records) expose data with no behavior — navigating through them is acceptable. Objects expose behavior and hide data — chaining through objects violates LoD.
- Builder patterns and fluent APIs that return the same type at each step are explicitly not violations.
- The Response For a Class (RFC) metric — methods potentially invoked in response to a method call — correlates with bug probability. Following LoD reduces RFC.
- Coupling Between Objects (CBO) measures how many external types a class references.
- At architecture level, visualize service-to-service call chains: any path longer than one hop suggests a potential violation.
- The Demeter paper notes that LoD tends to force narrow method-level dependencies but can lead to wide class-level interfaces because you introduce auxiliary methods rather than digging into structures. This trade-off requires system-level judgment.
- JetBrains IntelliJ includes a built-in "Law of Demeter" inspection.
## False Positives to Avoid
- Navigating through data structures (DTOs, records, configuration objects) is acceptable — LoD applies to objects with behavior, not plain data.
- Fluent APIs and builder patterns that return `this` or the same builder type are not violations — the chain stays on one object.
- A facade that deliberately aggregates multiple calls behind a single interface is not a violation — it's the recommended fix.
- Standard library traversals (e.g., `path.parent.name` in pathlib, or stream operations) are generally not violations.
|