Cosmic Module

O

Qubits of DPK

March 20, 2026

Core Open Source

Why Patterns Matter

Every single class in Fineract follows a set of design patterns. If you understand these patterns, you can navigate ANY file in the codebase — even one you've never seen before — because the structure is always the same.

Pattern 1 — CQRS (Command Query Responsibility Segregation)

Layman Explanation

Imagine a bank with two counters:
  • Counter A — only handles READING ("What is my balance?")
  • Counter B — only handles WRITING ("Transfer ₹5000 to Rahul")
They are completely separate. The person at Counter A never touches money. The person at Counter B never looks up balances.
This is CQRS. Fineract separates every operation into:
  • Query → Read data, return it, NO changes to DB
  • Command → Change data, trigger events, audit trail

In Code

Every domain has TWO service interfaces:
java
QUBITS OF DPK
1// READ side — only queries
2public interface LoanReadPlatformService {
3    LoanData retrieveLoan(Long loanId);
4    Collection<LoanData> retrieveAllLoans(SearchParameters searchParameters);
5}
6
7// WRITE side — only commands
8public interface LoanWritePlatformService {
9    CommandProcessingResult disburseLoan(Long loanId, JsonCommand command);
10    CommandProcessingResult makeRepayment(Long loanId, JsonCommand command);
11}

Why?

  • Read operations can be cached, scaled horizontally, run without locks
  • Write operations need transactions, audit logging, event publishing
  • Separating them keeps code clean and prevents accidental writes in read paths

Pattern 2 — The Command Pattern

Layman Explanation

Think of a restaurant. When you order food:
  1. #
    You tell the waiter (API layer)
  2. #
    The waiter writes it on a ticket (Command object)
  3. #
    The ticket goes to the kitchen (CommandHandler)
  4. #
    The kitchen cooks and sends back a result
The waiter doesn't cook. The kitchen doesn't take orders. Clean separation.

The Full Flow in Fineract

javascript
QUBITS OF DPK
1HTTP POST /loans/1/transactions?command=repayment
234 LoanApiResource.executeLoanTransaction()REST Controller (the waiter)
5        │  creates JsonCommand
67 PortfolioCommandSourceWritePlatformServiceCommand Bus (routes the ticket)
8        │  looks up the right handler
910 LoanRepaymentCommandHandler.handle()CommandHandler (the kitchen)
11        │  calls write service
1213 LoanWritePlatformServiceImpl.makeLoanRepayment()Business logic
14        │  saves to DB, publishes events
1516 CommandProcessingResultReturns resource ID + changes

JsonCommand — The Ticket

java
QUBITS OF DPK
1// JsonCommand wraps the raw HTTP request body
2public class JsonCommand {
3    private final String jsonCommand;           // raw JSON string
4    private final JsonElement parsedCommand;    // parsed JSON
5    private final Long resourceId;             // which entity (loan ID)
6    private final String transactionId;        // for idempotency
7
8    public BigDecimal bigDecimalValueOfParameterNamed(String paramName) { ... }
9    public LocalDate localDateValueOfParameterNamed(String paramName) { ... }
10    public Long longValueOfParameterNamed(String paramName) { ... }
11}

CommandHandler — The Kitchen

java
QUBITS OF DPK
1@Component
2@CommandType(entity = "LOAN", action = "REPAYMENT")  // ← routing key
3public class LoanRepaymentCommandHandler implements NewCommandSourceHandler {
4
5    @Override
6    public CommandProcessingResult handle(JsonCommand command) {
7        // 1. Validate the command
8        // 2. Call write service
9        // 3. Return result with resource ID
10        return loanWritePlatformService.makeLoanRepayment(
11            LoanTransactionType.REPAYMENT, command.getEntityId(), command);
12    }
13}

CommandProcessingResult — The Receipt

java
QUBITS OF DPK
1// Every write operation returns this
2public class CommandProcessingResult {
3    private final Long commandId;         // audit log ID
4    private final Long officeId;
5    private final Long groupId;
6    private final Long clientId;
7    private final Long loanId;
8    private final Long resourceId;        // the entity that was created/modified
9    private final String resourceExternalId;  // external ID
10    private final Map<String, Object> changes; // what changed
11}

Pattern 3 — Domain-Driven Design (DDD)

Layman Explanation

DDD says: organise your code around REAL business concepts, not technical layers.
Bad (technical layers):
javascript
QUBITS OF DPK
1src/controllers/
2src/services/
3src/repositories/
Everything for loans, clients, savings mixed together.
Good (DDD — Fineract's way):
javascript
QUBITS OF DPK
1portfolio/
2├── loanaccount/Loan bounded context
3│   ├── domain/Loan entity, business logic
4│   ├── service/LoanReadService, LoanWriteService
5│   ├── api/REST endpoints
6│   └── data/DTOs
7├── savings/Savings bounded context
8│   ├── domain/
9│   ├── service/
10│   ├── api/
11│   └── data/
12└── client/Client bounded context
13    ├── domain/
14    ├── service/
15    ├── api/
16    └── data/

The Standard 4-Layer Package Structure

Every domain in Fineract follows this exact structure:

Pattern 4 — Event-Driven Architecture

Layman Explanation

When something important happens in a bank (loan disbursed, repayment made), many different departments need to know:
  • Accounting must record a journal entry
  • SMS system must notify the customer
  • Audit log must record who did what
Instead of calling all of them directly, Fineract publishes an event and each department listens for the events they care about.
This is like a hospital's PA system: "Patient in Room 4 needs a doctor" — every relevant person who heard it responds.

In Code

java
QUBITS OF DPK
1// 1. Define the event
2public class LoanTransactionBusinessEvent implements BusinessEvent<LoanTransaction> {
3    private final LoanTransaction loanTransaction;
4    // getType() returns "LoanRepaymentMadeBusinessEvent"
5}
6
7// 2. Publish the event (in the write service)
8businessEventNotifierService.notifyPostBusinessEvent(
9    new LoanRepaymentMadeBusinessEvent(loanTransaction)
10);
11
12// 3. Listen for the event (in accounting module)
13@Component
14public class LoanRepaymentMadeEventListener
15        implements BusinessEventListener<LoanRepaymentMadeBusinessEvent> {
16
17    @Override
18    public void onBusinessEvent(LoanRepaymentMadeBusinessEvent event) {
19        LoanTransaction transaction = event.get();
20        // Create journal entries for this repayment
21        accountingProcessorForLoan.createJournalEntriesForLoan(...);
22    }
23}

Event Flow

javascript
QUBITS OF DPK
1Loan Repayment Made
23BusinessEventNotifierService.notifyPostBusinessEvent()
45        ├──▶ AccountingEventListenerCreates journal entries
6        ├──▶ SmsEventListenerSends SMS to customer
7        ├──▶ HookEventListenerFires webhook to external system
8        └──▶ AuditEventListenerRecords in audit log
Key benefit: The loan module doesn't know about accounting. Accounting doesn't know about SMS. They're completely decoupled.

Pattern 5 — Repository Pattern (Spring Data JPA)

Layman Explanation

A repository is like a filing cabinet. You ask it for a file, it gives it to you. You don't care how it's stored — magnetic disk, SSD, cloud — you just call findById().

In Code

java
QUBITS OF DPK
1// Repository interface — Spring generates the implementation
2@Repository
3public interface LoanRepository extends JpaRepository<Loan, Long> {
4
5    // Spring auto-generates: SELECT * FROM m_loan WHERE id = ?
6    Optional<Loan> findById(Long loanId);
7
8    // Spring auto-generates the SQL from the method name!
9    List<Loan> findByClientIdAndLoanStatus(Long clientId, Integer loanStatus);
10
11    // Custom JPQL query
12    @Query("SELECT l FROM Loan l WHERE l.client.id = :clientId AND l.loanStatus = 300")
13    List<Loan> findActiveLoansForClient(@Param("clientId") Long clientId);
14}

The Complete Request Flow — Putting It All Together

javascript
QUBITS OF DPK
11. Client sends:  POST /fineract-provider/api/v1/loans/42/transactions?command=repayment
2                  Body: {"transactionDate": "04 Mar 2025", "transactionAmount": 5000}
3
42. Spring Security validates username/password + tenant ID header
5
63. LoanTransactionsApiResource.executeLoanTransaction() receives request
7Creates JsonCommand from request body
8
94. PortfolioCommandSourceWritePlatformService routes to handler
10Finds @CommandType(entity="LOAN_TRANSACTION", action="REPAYMENT")
11
125. LoanRepaymentCommandHandler.handle(JsonCommand) executes
13Calls loanWritePlatformService.makeLoanRepayment()
14
156. LoanWritePlatformServiceImpl.makeLoanRepayment()
16Loads Loan from LoanRepository
17Validates repayment amount and date
18Creates LoanTransaction entity
19Updates loan balance, overdue status
20Saves via LoanRepository
21Publishes LoanRepaymentMadeBusinessEvent
22
237. Event listeners fire:
24AccountingListener creates journal entries
25SmsListener sends SMS
26
278. Returns CommandProcessingResult { resourceId: 87 (transaction ID) }
28
299. Client receives: HTTP 200 { "resourceId": 87 }

Mentor Cheat Sheet