Skip to content

System-Level Cohesion & Coupling

Introduction

Developers are first taught high cohesion and low coupling at the class level.

  • Cohesion: A class should have a single, focused responsibility.
  • Coupling: Classes should depend on each other minimally.

But in large-scale systems, these principles must be applied at higher levels:

  • Modules (packages, libraries).
  • Components (bounded contexts).
  • Services (microservices in distributed architectures).

A highly cohesive and loosely coupled system-level design ensures that:

  • Teams can develop and deploy independently.
  • Changes remain localized.
  • The system scales with business and technical demands.

High Cohesion at the System Level

At the system level, cohesion means grouping related functionality within the same service/module.
A cohesive service:

  • Encapsulates a single domain responsibility.
  • Owns its data and logic.
  • Minimizes overlap with other services.

Example: Order Service (Cohesive)

java
@Service
public class OrderService {
    private final OrderRepository repository;
    private final PaymentClient paymentClient;

    public OrderService(OrderRepository repository, PaymentClient paymentClient) {
        this.repository = repository;
        this.paymentClient = paymentClient;
    }

    public void placeOrder(Order order) {
        repository.save(order);
        paymentClient.charge(order);
    }
}

✅ Order service manages orders only.
❌ Does not handle unrelated domains like Inventory or Shipping.


Low Coupling at the System Level

At the system level, low coupling means services depend on each other through stable contracts, not internal details.

  • Dependencies are via APIs, not databases.
  • Failures in one service don’t cascade widely.
  • Services can evolve independently.

Example: Payment via Interface

java
// Boundary contract
public interface PaymentClient {
    void charge(Order order);
}

// Stripe adapter
@Component
public class StripePaymentClient implements PaymentClient {
    public void charge(Order order) {
        // call Stripe API
    }
}

✅ OrderService depends on PaymentClient interface.
✅ Stripe implementation can be swapped for Razorpay/PayPal.


Cohesion & Coupling in Monoliths vs Microservices

In Monoliths

  • Cohesion: Related domains grouped in modules/packages.
  • Coupling: Modules interact via interfaces, not shared state.

In Microservices

  • Cohesion: Each service owns its bounded context (DDD).
  • Coupling: Services interact via APIs, not shared databases.

Bad Example (Tight Coupling)

  • OrderService queries Inventory DB directly.
  • Schema change in Inventory breaks Orders.

Good Example (Loose Coupling)

  • OrderService calls Inventory API.
  • Database schema hidden behind API boundary.

System-Level Cohesion & Coupling Metrics

Signs of Low Cohesion

  • A service has mixed responsibilities (e.g., Order + Shipping logic together).
  • Frequent cross-team ownership disputes.

Signs of Tight Coupling

  • Services share a database.
  • Services have cyclic dependencies.
  • A small change in one service requires redeploying others.

Heuristics

  • If a change requires edits in multiple services → low cohesion.
  • If a small change in one breaks others → tight coupling.

Real-World Case Studies

1. Amazon

  • High Cohesion: “Order Service” manages only the order lifecycle.
  • Low Coupling: Communicates with Payment & Inventory via APIs.
  • Result: Independent scaling of services.

2. Netflix

  • High Cohesion: Each microservice focuses on a single concern (e.g., Recommendation, Catalog).
  • Low Coupling: Services communicate asynchronously via APIs and queues.
  • Result: Scales globally with minimal ripple effects.

3. Uber

  • Initially: tightly coupled monolith → “spaghetti code.”
  • Refactor: domain-driven microservices with APIs.
  • Improved deployment speed & failure isolation.

Common Pitfalls

  1. Shared Database Across Services

    • Orders, Payments, and Inventory share one schema.
    • Breaks cohesion (multiple domains mixed).
    • Breaks coupling (schema change affects all).
  2. God Services

    • “Utility Service” doing logging, validation, notifications.
    • Becomes bottleneck and point of failure.
  3. Synchronous Over-Coupling

    • Services chained via synchronous calls (Order → Payment → Inventory → Shipping).
    • High latency and cascading failures.

Solution: Introduce asynchronous communication (event-driven architecture).


Extended Java Case Study

Scenario: Order & Inventory Services

Bad Design (Coupling via Shared DB):

java
// OrderService directly queries Inventory table
public class OrderService {
    public boolean placeOrder(Order order) {
        int stock = jdbc.query("SELECT stock FROM inventory WHERE id=?", order.getId());
        if (stock > 0) { /* proceed */ }
        return false;
    }
}

❌ OrderService depends directly on Inventory DB schema.
❌ Any schema change breaks OrderService.

Good Design (API Boundary):

java
// OrderService calls Inventory API
public class OrderService {
    private final InventoryClient client;

    public OrderService(InventoryClient client) {
        this.client = client;
    }

    public boolean placeOrder(Order order) {
        if (client.hasStock(order.getId())) { /* proceed */ }
        return false;
    }
}

// InventoryClient interface
public interface InventoryClient {
    boolean hasStock(String productId);
}

// Implementation (HTTP call)
public class HttpInventoryClient implements InventoryClient {
    public boolean hasStock(String productId) {
        // REST API call to Inventory service
        return true;
    }
}

✅ OrderService decoupled from Inventory DB schema.
✅ Inventory evolves independently.


Interview Prep

Q1: What does high cohesion mean at the system level?

Answer: Each service/module owns one domain responsibility and encapsulates related data and logic.

Q2: How do you achieve low coupling between services?

Answer: Use API contracts instead of direct DB access, avoid cyclic dependencies, and introduce asynchronous communication where possible.

Q3: What happens when services share a database?

Answer: Tight coupling, schema fragility, loss of independence. One schema change can break multiple services.

Q4: How do cohesion and coupling affect scalability?

Answer: High cohesion allows scaling the right services independently. Low coupling ensures scaling one service doesn’t require scaling others unnecessarily.

Q5: Give a real-world example.

Answer: Amazon’s Order Service manages orders only (cohesion) and communicates with Payment via API (low coupling).


Visualizing Cohesion & Coupling

mermaid
graph TD
  A[Order Service] -->|API| B[Payment Service]
  A -->|API| C[Inventory Service]
  C -.->|DB owned only by Inventory| D[(Inventory DB)]

✅ Each service owns its data (high cohesion).
✅ Communication via APIs (low coupling).


Key Takeaways

  • High Cohesion → Each service/module should own one domain responsibility.
  • Low Coupling → Services interact via APIs, not shared DBs or internal details.
  • Together, they allow independent scaling, deployment, and evolution.

Next Lesson

Having mastered cohesion & coupling at the system level, we now turn to Domain-Driven Design Principles (DDD Lite) — modeling complex domains using bounded contexts and ubiquitous language.

Continue to DDD Principles →


Connect: LinkedIn

© 2025 Official CTO. All rights reserved.