Appearance
Coupling and Cohesion
The fundamental forces that shape software architecture
The Core Tension
Every architectural decision we make is fundamentally about managing two forces: coupling and cohesion. Understanding these concepts is essential because they explain why we structure code the way we do, not just how.
Coupling: The Cost of Dependencies
Coupling occurs when one piece of code depends on another. The tighter the coupling, the harder it becomes to change one piece without affecting the other.
Types of Coupling
Direct Dependencies The most obvious form—when code directly imports, calls, or references another piece of code.
typescript
// Tight coupling: OrderService directly depends on EmailService
class OrderService {
constructor(private emailService: EmailService) {}
}Shared Data When multiple components read and write the same data structures or database tables.
typescript
// Both services manipulate the same user record
userService.updateLastLogin(userId);
notificationService.checkUserLastLogin(userId);Temporal Coupling When code must execute in a specific order to work correctly.
typescript
// Must call these in order
database.connect();
database.authenticate();
database.query(); // Fails if previous steps weren't doneShared Resources When components compete for or share system resources like files, network connections, or memory.
The Cost of Coupling
- Change Amplification: Modifying one component requires changes to all coupled components
- Testing Complexity: Can't test components in isolation
- Cognitive Load: Must understand multiple components to work on one
- Deployment Risk: Can't deploy components independently
Cohesion: The Power of Relatedness
Cohesion measures how well the elements within a module belong together. High cohesion means that code that changes together, lives together.
Signs of High Cohesion
Single Purpose All code in the module works toward the same goal.
typescript
// High cohesion: All methods relate to order processing
class OrderProcessor {
validateOrder() {}
calculateTotals() {}
applyDiscounts() {}
processPayment() {}
}Common Data Functions that operate on the same data structures are grouped together.
Temporal Cohesion Operations that happen at the same time in a business process are co-located.
Communicational Cohesion Code that works with the same input or produces related output stays together.
Benefits of High Cohesion
- Easier to Understand: Related functionality is in one place
- Easier to Modify: Changes are localized
- Better Reusability: Cohesive modules have clear, focused purposes
- Reduced Coupling: Internal coupling within a module is better than coupling between modules
The Fundamental Principle
Things that change together should live together. Things that change separately should be decoupled.
This principle drives our architectural decisions:
- Increase cohesion by grouping related functionality
- Decrease coupling between unrelated components
- Accept coupling within cohesive boundaries
Strategic Coupling
Not all coupling is bad. The key is being intentional about where we allow it.
Good Coupling
Within Cohesive Boundaries Components within a module that share a common purpose should be coupled.
typescript
// Good: Tightly coupled within the Order module
class Order {
items: OrderItem[];
customer: Customer;
pricing: PricingStrategy;
}To Stable Abstractions Depending on stable interfaces rather than volatile implementations.
typescript
// Good: Coupled to interface, not implementation
constructor(private repository: IOrderRepository) {}Bad Coupling
Cross-Domain Business domains that could evolve independently are tightly coupled.
To Infrastructure Business logic depends on specific technical implementations.
Circular Dependencies Components that depend on each other create change deadlocks.
Decoupling Strategies
Dependency Inversion
Depend on abstractions, not concretions. Let high-level modules define the interfaces they need.
Event-Driven Communication
Use events to communicate between modules without direct dependencies.
typescript
// Decoupled: Order doesn't know about inventory
orderEvents.emit('orderPlaced', order);
// Inventory subscribes to events independentlyShared Kernel
Extract truly shared concepts into a stable, carefully managed shared library.
Anti-Corruption Layer
Create a translation layer between different models to prevent coupling to external systems.
The Architecture Connection
Our architectural patterns are tools for managing coupling and cohesion:
- Clean Architecture uses layers to control coupling direction
- Modular Monolith creates cohesive modules with controlled coupling points
- Dependency Inversion decouples high-level from low-level modules
- Use Cases create cohesive units of business logic
Practical Guidelines
When to Decouple
- Different rates of change
- Different teams working on the code
- Different deployment schedules
- Different business concerns
- Different technical requirements
When to Accept Coupling
- Within a single business transaction
- For cohesive domain concepts
- When the cost of decoupling exceeds the benefit
- For stable, unlikely-to-change relationships
Warning Signs
Too Much Coupling
- Shotgun surgery: One change requires many file modifications
- Feature envy: Code that uses another module's data more than its own
- Inappropriate intimacy: Classes that know too much about each other
Too Little Cohesion
- God classes: Classes that do everything
- Scattered functionality: Related code spread across many files
- Data classes: Classes with only data, no behavior
The Balance
Perfect decoupling is neither achievable nor desirable. The goal is intentional, strategic coupling that:
- Keeps related things together (cohesion)
- Separates unrelated things (decoupling)
- Creates clear, stable boundaries
- Enables independent change
Remember: Architecture is the art of drawing boundaries in the right places. Coupling and cohesion tell us where those boundaries should be.
Applied In
- Clean Architecture - Managing coupling through the Dependency Rule
- Modular Monolith - Creating cohesive modules with controlled coupling
- Modular Monolith in NestJS - Module boundaries in practice
- Clean Architecture in NestJS Modules - Layer cohesion and coupling
- Acceptance Testing Guidelines - Decoupling tests from implementation
Further Reading
- Structured Design by Larry Constantine and Edward Yourdon - Original source of coupling and cohesion concepts
- Coupling and Cohesion by Vladimir Khorikov
- Clean Code by Robert C. Martin - Chapter 10: Classes