Principle of Least Knowledge at Service Boundaries
Introduction
In large-scale distributed systems, services must collaborate to fulfill business workflows.
However, if services know too much about each other’s internals, the system becomes fragile:
- A change in one service breaks others.
- Deployments require coordination.
- Teams lose autonomy.
To prevent this, architects apply the Principle of Least Knowledge (also known as the Law of Demeter) at service boundaries.
The rule: A service should only talk to its direct collaborators through stable contracts, and know as little as possible about others.
Why the Principle of Least Knowledge Matters
- Reduced Coupling – Services interact through narrow, stable contracts.
- Team Autonomy – Each team can evolve its service without ripple effects.
- Resilience – Failures are contained; fewer dependencies mean fewer breakages.
- Security – Internal details remain hidden, minimizing attack surfaces.
The Law of Demeter Refresher (Class Level)
At the class level, Law of Demeter says:
- Don’t chain too many calls (
a.getB().getC().doSomething()
). - Instead, interact with direct collaborators.
At the system level, this translates to:
- Don’t expose one service’s internals (schema, nested objects) to others.
- Only expose what’s necessary via APIs.
Bad Example – Violating the Principle
// OrderService calling PaymentService internals
public class OrderService {
private PaymentService paymentService;
public void placeOrder(Order order) {
paymentService.getPaymentRepository()
.getPaymentGateway()
.charge(order);
}
}
❌ OrderService knows about repository + gateway internals of PaymentService.
❌ Any internal refactor in PaymentService breaks OrderService.
Good Example – Following the Principle
// Contract exposed by PaymentService
public interface PaymentService {
void charge(Order order);
}
// OrderService only depends on the contract
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder(Order order) {
paymentService.charge(order);
}
}
✅ OrderService only knows about charge()
.
✅ Payment internals hidden.
✅ Payment team can refactor freely.
Service Boundaries in Microservices
Anti-Pattern: Shared Database
- OrderService queries Payment DB directly.
- Tight coupling to schema.
Correct Pattern: API Boundary
- OrderService calls Payment API
/charge
. - Schema hidden behind API.
Real-World Case Studies
1. Amazon
- Teams not allowed to access each other’s DB.
- APIs enforced as boundaries.
- Result: Teams scale independently.
2. Netflix
- Recommendation service doesn’t query Viewing DB directly.
- Talks to Viewing API.
- Ensures independence and resilience.
3. Banking Systems
- Compliance APIs expose only what’s required.
- Internal rules hidden.
- Prevents accidental violations.
Common Pitfalls
Leaky APIs
- Expose DB schema or nested objects.
- Violates least knowledge.
Chatty APIs
- Require too many fine-grained calls.
- Leads to performance issues.
Tight Coupling via Libraries
- Services share the same internal library.
- Breaks independence.
Extended Java Case Study
Without Least Knowledge
// InventoryService exposes nested structures
public class InventoryService {
public InventoryRepository getRepository() { return repo; }
}
// OrderService chained calls into Inventory internals
orderService.getInventoryService()
.getRepository()
.getStockDAO()
.checkStock(id);
❌ OrderService depends on Inventory internals.
With Least Knowledge
// InventoryService contract
public interface InventoryService {
boolean hasStock(String productId);
}
// OrderService depends only on the API
public class OrderService {
private final InventoryService inventoryService;
public OrderService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
public void placeOrder(Order order) {
if (inventoryService.hasStock(order.getId())) { ... }
}
}
✅ OrderService only depends on hasStock()
.
✅ Inventory can refactor internals freely.
Interview Prep
Q1: What is the Principle of Least Knowledge?
Answer: A service should only talk to its direct collaborators via stable contracts, and not depend on internal details of other services.
Q2: Why is it important in microservices?
Answer: Prevents tight coupling across services, enabling team autonomy and independent evolution.
Q3: What’s an example of violating it?
Answer: Service A querying Service B’s database directly.
Q4: How can you enforce it in Java?
Answer: Define contracts via interfaces or APIs, hide implementation details, and avoid exposing nested structures.
Q5: What’s the relationship with Law of Demeter?
Answer: Law of Demeter at class level → Least Knowledge at system/service level. Both reduce coupling by avoiding dependency on indirect details.
Visualizing Least Knowledge
graph TD
A[Order Service] -->|charge()| B[Payment API]
B --> C[(Payment DB)]
A -.->|Does NOT see DB| C
✅ Order talks only to Payment API.
✅ Payment DB hidden.
Key Takeaways
- Principle of Least Knowledge = services should only depend on minimal, stable contracts.
- Prevents fragile coupling across services.
- Builds resilience, autonomy, and security.
- Strongly enforced at Amazon, Netflix, and banks.
Next Lesson
Next, we’ll explore Security Principles in Architecture — least privilege, defense in depth, and failing securely.
Continue to Security Principles →