Back to Decomposition & DDD
Q5Question 5 of 9

The Decomposition design pattern

Key Concepts
  • Decompose by business capability: each service maps to what the business does (Payments, Loans, Notifications).
  • Decompose by DDD subdomain: each bounded context becomes a service with its own ubiquitous language.
  • Decompose by transaction: keep operations that must be atomic together in one service to avoid unnecessary Sagas.
  • Decompose by scalability: extract components with very different load profiles so they scale independently.
  • In practice combine all four: start with capabilities, refine with DDD, keep consistent operations together, carve out hotspots.
  • The goal is high cohesion inside a service and low coupling between services.
  • The test: a well-decomposed service changes for one reason and talks to others rarely.

Definition

Decompose by business capability or DDD subdomain for clear ownership, keep tightly coupled transactions in one service, and split by scalability when one part has very different load, always maximizing cohesion and minimizing coupling.
  • By business capability – each service maps to a thing the business does, such as Payments or Lending.
  • By subdomain (DDD) – each service maps to a bounded context where a term has one precise meaning.
  • By transaction – operations that must share a consistency boundary stay in one service.
  • By scalability – a component with very different load is split out so it can scale on its own.
  • Goal – high cohesion inside a service, low coupling between services.

At National Bank of India, decomposition is deciding how to divide one giant bank into departments. By business capability means one department per job: Payments, Loans, Cards. By subdomain means dividing along how the business actually thinks, so the people who talk about 'sanctioning' and 'EMI' form the Loan department. By transaction means keeping things that must balance together in one room. By scalability means the festival-season payments counter gets its own building so it can add tellers without the quiet loans desk growing too.

Understanding the Concepts

What is decomposition by business capability?

Each service owns a capability the business offers. You list what the bank does and each becomes a service.

JAVA
package com.bank.service;

// Each service owns one business capability end to end.

// PaymentService: everything about payments
public class PaymentService {
    public void initiateTransfer(String fromId, String toId, java.math.BigDecimal amount) {
        // Handles transfer initiation, settlement, reversal
    }
}

// LoanService: everything about loans
public class LoanService {
    public void sanctionLoan(String customerId, java.math.BigDecimal principal) {
        // Handles sanction, EMI calculation, disbursement
    }
}

// NotificationService: everything about notifications
public class NotificationService {
    public void sendSms(String customerId, String message) {
        // Handles SMS, email, push
    }
}
TEXT
Boundary is stable because business capabilities change slowly.
Ownership is clear: one team owns "payments" end to end.

WRONG: Split by technical layer (ControllerService, DAOService)
  -> Every feature spans every service. Distributed monolith.

What is decomposition by DDD subdomain?

A bounded context is a region where a term has one precise meaning. Each bounded context becomes a service with its own model.

JAVA
package com.bank.lending.model;

import java.math.BigDecimal;

// In the Lending context, "Account" means a loan account.
public class LoanAccount {
    private String loanAccountId;
    private BigDecimal principal;
    private BigDecimal interestRate;
    private int tenureMonths;
    // This is NOT the same as a deposit account.
}
JAVA
package com.bank.deposits.model;

import java.math.BigDecimal;

// In the Deposits context, "Account" means a savings/current account.
public class DepositAccount {
    private String accountId;
    private BigDecimal balance;
    private String accountType; // SAV, CUR
    // This is NOT the same as a loan account.
}
TEXT
"Account" means different things in different contexts:
  Deposits: savings account with a balance
  Lending: loan account with principal and EMI
  Cards: credit card account with a limit

Each context has its own model. No shared "Account" class.

What is decomposition by transaction?

Keep operations that must be atomic together in one service. If two pieces of data must always change together, splitting them forces a Saga.

JAVA
package com.bank.ledger;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class LedgerService {

    @Transactional
    public void postEntry(String accountId, BigDecimal debit, BigDecimal credit) {
        // Debit and credit of a single ledger posting MUST be atomic.
        // Keep them in ONE service, ONE transaction.
        // Splitting them would force a Saga for something that should be simple.
    }
}
TEXT
Rule: Keep strongly consistent operations in one service.
Accept Sagas only across boundaries where eventual consistency is fine.

Keep together: debit + credit of a single ledger posting
Split with Saga: debit Rajesh (Account Service) + credit Priya (Account Service)
  -> Two different accounts, two different aggregates, Saga is appropriate.

What is decomposition by scalability?

Split out a component whose load profile differs sharply from the rest. If fraud scoring needs 10x CPU at peak, extract it.

DIAGRAM
Load profiles at National Bank of India:

  Component        | Normal Load  | Peak Load   | Scale Factor
  ─────────────────┼──────────────┼─────────────┼─────────────
  Account queries   | 1000 req/s  | 2000 req/s  | 2x
  Payment transfers | 500 req/s   | 5000 req/s  | 10x
  Fraud scoring    | 500 req/s   | 5000 req/s  | 10x
  Notifications    | 200 req/s   | 200 req/s   | 1x

Payment and Fraud need 10x scaling at festival peak.
Notifications barely change.
Extract Payment and Fraud so they scale without dragging Notifications.

How do the four strategies combine?

In practice you combine all four. Start with business capabilities, refine with DDD subdomains, keep strongly consistent operations together, and carve out scalability hotspots.

TEXT
Combined decomposition:
  1. List business capabilities -> Account, Payment, Loan, Notification, Fraud
  2. Refine with DDD -> "Account" means deposit in Deposits context,
     loan in Lending context
  3. Transaction check -> debit+credit of one posting stays in one service
  4. Scalability check -> Fraud scoring extracted (10x peak scaling needed)

How do you test if a boundary is correct?

The test: a well-decomposed service changes for one reason and talks to others rarely. If it changes whenever anything changes and chatters constantly, the cut was wrong.

TEXT
GOOD boundary:
  Payment Service changes only when payment logic changes.
  Talks to Account Service once per transfer.
  Can be deployed independently.

BAD boundary:
  PaymentController Service changes with every feature.
  Calls BusinessLogic Service and DAOService for everything.
  Must deploy with both. Distributed monolith.

What are common mistakes?

The most common mistake is decomposing by technical layer instead of business capability.

JAVA
package com.bank.antipattern;

// WRONG: Technical layer decomposition.
// Every feature spans all services.
public class WrongDecomposition {
    // api-gateway-service: routes
    // business-logic-service: rules
    // database-service: persistence
    // Result: distributed monolith.
}

// CORRECT: Business capability decomposition.
public class CorrectDecomposition {
    // payment-service: owns payment logic + payment DB
    // account-service: owns account logic + account DB
    // Each service is self-contained.
}

What should a senior engineer know?

The goal is high cohesion and low coupling. A well-decomposed service changes for one reason and talks to others rarely. If it changes whenever anything changes, redraw the boundary.

Visual Overview

DIAGRAM
  Four Decomposition Strategies:

  ┌────────────────────────────────────────────────┐
  │  1. BUSINESS CAPABILITY                         │
  │  What does the business do?                     │
  │  Payments │ Loans │ Accounts │ Notifications    │
  └────────────────────────────────────────────────┘
              │
              ▼
  ┌────────────────────────────────────────────────┐
  │  2. DDD SUBDOMAIN                               │
  │  Where does a term have ONE meaning?            │
  │  "Account" = deposit in Deposits                │
  │  "Account" = loan in Lending                    │
  └────────────────────────────────────────────────┘
              │
              ▼
  ┌────────────────────────────────────────────────┐
  │  3. TRANSACTION BOUNDARY                        │
  │  Must these change atomically?                  │
  │  YES -> keep in one service                     │
  │  NO  -> Saga is acceptable                      │
  └────────────────────────────────────────────────┘
              │
              ▼
  ┌────────────────────────────────────────────────┐
  │  4. SCALABILITY HOTSPOT                         │
  │  Does this component need 10x more resources?   │
  │  YES -> extract for independent scaling         │
  └────────────────────────────────────────────────┘

Key Takeaway

The Decomposition pattern is the set of strategies for deciding where to draw service boundaries, and the whole goal is high cohesion inside a service and low coupling between services. I use four strategies together: business capability, DDD subdomain, transaction boundary, and scalability hotspot.

At National Bank of India, I start with business capabilities: each service owns something the business does, such as Payments, Lending, or Notifications. I refine with DDD subdomains so 'account' means a deposit account in the Deposits context and a loan account in the Lending context. I keep strongly consistent operations together so debit and credit of a single ledger posting stay in one service. And I extract scalability hotspots like fraud scoring that need 10x resources at peak.

The test I apply to every boundary is whether the resulting service changes for one reason and talks to others rarely. If it changes whenever anything changes and chatters constantly over the network, the cut was wrong and I redraw it.

Remember one sentence: decompose by business capability, refine with DDD subdomains, keep atomic operations together, extract scalability hotspots, and always check that the resulting service has high cohesion and low coupling.

---

Share
Use ← → arrow keys to navigate