Definition
- 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.
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
}
}
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.
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.
}
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.
}
"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.
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.
}
}
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.
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.
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.
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.
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
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.
---