Mục tiêu của Dự án
Đây là bài tập cuối chuỗi. Bạn sẽ tự tay xây dựng một Mini Core Banking hoàn chỉnh, áp dụng đồng thời mọi nguyên lý đã học:
- ✅ Phần 1: Kế toán kép với bảng Ledger immutable
- ✅ Phần 2: CIF, CASA, Lending domain models
- ✅ Phần 3: ACID transactions, Pessimistic Locking, Idempotency
- ✅ Phần 4: Event-driven với Outbox Pattern, kiến trúc Service
- ✅ Phần 5: Cấu trúc thông điệp chuẩn (ISO-inspired)
- ✅ Phần 6: Audit trail, data classification
Bạn có thể dùng bất kỳ ngôn ngữ nào: Go, Java, Python, Node.js, .NET — nguyên lý không thay đổi.
Bước 1: Thiết kế Database Schema
Đây là nền tảng. Làm đúng bước này, mọi thứ còn lại sẽ tự nhiên hơn.
-- ============================================================
-- 1. CUSTOMERS (CIF)
-- ============================================================
CREATE TABLE customers (
cif_number VARCHAR(20) PRIMARY KEY,
customer_type VARCHAR(15) NOT NULL CHECK (customer_type IN ('INDIVIDUAL','CORPORATE')),
full_name VARCHAR(255) NOT NULL,
id_number VARCHAR(30) UNIQUE NOT NULL,
kyc_status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================
-- 2. ACCOUNTS (CASA)
-- ============================================================
CREATE TABLE accounts (
account_number VARCHAR(20) PRIMARY KEY,
cif_number VARCHAR(20) NOT NULL REFERENCES customers(cif_number),
account_type VARCHAR(30) NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'VND',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
current_balance BIGINT NOT NULL DEFAULT 0,
available_balance BIGINT NOT NULL DEFAULT 0,
version BIGINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================
-- 3. LEDGER ENTRIES (Double-Entry Bookkeeping)
-- ============================================================
CREATE TABLE ledger_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
transaction_id UUID NOT NULL,
account_number VARCHAR(20) NOT NULL REFERENCES accounts(account_number),
entry_type CHAR(6) NOT NULL CHECK (entry_type IN ('DEBIT','CREDIT')),
amount BIGINT NOT NULL CHECK (amount > 0),
currency CHAR(3) NOT NULL,
balance_after BIGINT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Ngăn sửa/xóa ledger
CREATE RULE no_update_ledger AS ON UPDATE TO ledger_entries DO INSTEAD NOTHING;
CREATE RULE no_delete_ledger AS ON DELETE TO ledger_entries DO INSTEAD NOTHING;
-- ============================================================
-- 4. TRANSACTIONS (Idempotency Control)
-- ============================================================
CREATE TABLE financial_transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
idempotency_key VARCHAR(64) UNIQUE NOT NULL,
type VARCHAR(30) NOT NULL, -- 'DEPOSIT','WITHDRAWAL','TRANSFER','FEE'
status VARCHAR(20) NOT NULL DEFAULT 'PROCESSING',
from_account VARCHAR(20),
to_account VARCHAR(20),
amount BIGINT NOT NULL,
currency CHAR(3) NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- ============================================================
-- 5. OUTBOX (Event Publishing đảm bảo At-Least-Once)
-- ============================================================
CREATE TABLE outbox_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
topic VARCHAR(100) NOT NULL,
payload JSONB NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
published_at TIMESTAMPTZ
);
-- ============================================================
-- 6. AUDIT LOG
-- ============================================================
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
entity_type VARCHAR(50) NOT NULL,
entity_id VARCHAR(50) NOT NULL,
action VARCHAR(50) NOT NULL,
actor_id VARCHAR(50) NOT NULL,
before_data JSONB,
after_data JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Bước 2: Implement Logic Chuyển Tiền (Trái tim của hệ thống)
Đây là use case quan trọng nhất. Phải đảm bảo tất cả bước dưới đây trong một Database Transaction duy nhất:
FLOW: POST /v1/transfers
Header: Idempotency-Key: <uuid-from-client>
STEP 1: Kiểm tra Idempotency Key
→ Nếu đã xử lý rồi: return kết quả cũ (200 OK)
→ Nếu đang xử lý: return 409 Conflict
→ Nếu chưa có: tiếp tục
STEP 2: Validate input
→ amount > 0
→ from_account != to_account
→ currency hợp lệ
STEP 3: BEGIN DATABASE TRANSACTION
STEP 3a: Lock hai tài khoản (theo thứ tự ID để tránh deadlock)
→ SELECT ... FOR UPDATE WHERE account_number IN (from, to) ORDER BY account_number
STEP 3b: Kiểm tra điều kiện nghiệp vụ
→ available_balance(from) >= amount
→ status(from) = 'ACTIVE'
→ status(to) = 'ACTIVE'
STEP 3c: Cập nhật số dư
→ UPDATE accounts SET current_balance -= amount, available_balance -= amount WHERE from
→ UPDATE accounts SET current_balance += amount, available_balance += amount WHERE to
STEP 3d: Ghi Double-Entry Ledger (2 bút toán)
→ INSERT DEBIT entry (from_account, amount, balance_after_deduction)
→ INSERT CREDIT entry (to_account, amount, balance_after_addition)
STEP 3e: Ghi financial_transaction record (status = COMPLETED)
STEP 3f: Ghi idempotency_key record
STEP 3g: Ghi outbox_events (topic: 'transfer.completed')
STEP 3h: Ghi audit_log
STEP 4: COMMIT TRANSACTION
STEP 5: Return 202 Accepted + transaction_id
Bước 3: Viết Invariant Check
Thêm một endpoint nội bộ (hoặc cron job) để liên tục kiểm tra tính toàn vẹn của sổ cái:
-- Chạy cái này hàng ngày, alert nếu imbalance != 0
SELECT
currency,
SUM(CASE WHEN entry_type = 'DEBIT' THEN amount ELSE 0 END) AS total_debits,
SUM(CASE WHEN entry_type = 'CREDIT' THEN amount ELSE 0 END) AS total_credits,
SUM(CASE WHEN entry_type = 'DEBIT' THEN amount ELSE 0 END) -
SUM(CASE WHEN entry_type = 'CREDIT' THEN amount ELSE 0 END) AS imbalance
FROM ledger_entries
GROUP BY currency
HAVING SUM(CASE WHEN entry_type = 'DEBIT' THEN amount ELSE 0 END) !=
SUM(CASE WHEN entry_type = 'CREDIT' THEN amount ELSE 0 END);
Bước 4: Stress Test — Bài kiểm tra cuối cùng
Sau khi xây dựng xong, bạn PHẢI stress test bằng cách gửi nhiều request concurrent:
# Dùng k6 (JavaScript load testing tool)
# Kịch bản: 100 user cùng chuyển tiền đồng thời từ tài khoản A sang B
k6 run --vus 100 --duration 30s transfer_test.js
# Sau khi test xong, kiểm tra:
# 1. Tổng tiền trong hệ thống có bị thay đổi không?
# 2. Sổ cái có cân bằng không? (DEBIT = CREDIT)
# 3. Có giao dịch nào bị trùng lặp không?
# 4. Có tài khoản nào bị âm không?
// transfer_test.js
import http from 'k6/http';
import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
export default function () {
http.post('http://localhost:8080/v1/transfers', JSON.stringify({
from_account: 'ACC001',
to_account: 'ACC002',
amount: 1000, // 1.000 đồng
currency: 'VND',
description: 'Stress test transfer'
}), {
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': uuidv4(), // Mỗi request có key riêng
}
});
}
Checklist hoàn thành dự án
Core Banking Logic
- Double-entry ledger hoạt động đúng (DEBIT = CREDIT sau mọi giao dịch)
- Ledger entries là immutable (không thể UPDATE/DELETE)
- Balance invariant check chạy được và trả về 0
Concurrency & Safety
- Pessimistic locking ngăn race condition
- Lock order nhất quán (tránh deadlock)
- Idempotency key hoạt động (gửi 10 lần cùng key → chỉ 1 giao dịch)
- Tài khoản không bao giờ bị âm
Reliability
- Outbox pattern đảm bảo event không bị mất
- API trả về 202 Accepted (không block chờ hoàn tất)
- Audit log ghi đầy đủ mọi thao tác
Stress Test
- 100 concurrent transfers → không có tiền bị mất/tạo ra
- Ledger cân bằng sau stress test
- Không có deadlock error trong logs
Bước tiếp theo sau khi hoàn thành
Khi bạn đã build được một Mini Core Banking hoạt động đúng, bạn có thể tiếp tục mở rộng:
- Thêm Lending module: Tạo khoản vay, tính lãi tự động, thu nợ định kỳ.
- Tích hợp ISO 8583: Xây dựng một Payment Switch đơn giản nhận thông điệp thẻ và gọi vào Core.
- CQRS Read Side: Thêm Elasticsearch để tìm kiếm lịch sử giao dịch cực nhanh.
- Rate Limiting & Fraud Detection: Phát hiện giao dịch bất thường theo Rule Engine.
- Nghiên cứu Apache Fineract: Source code mã nguồn mở của một Core Banking thực tế.
Tài nguyên tham khảo mở rộng
| Tài nguyên | Loại | Nội dung |
|---|---|---|
| Apache Fineract | Open Source | Core Banking đầy đủ cho tài chính vi mô |
| jPOS Project | Framework + Sách | ISO 8583, payment switch |
| Ledger.io Blog | Blog kỹ thuật | Double-entry accounting code |
| Thought Machine Blog | Blog kiến trúc | Vault Core — Core Banking hiện đại |
| Mambu Dev Portal | Docs | SaaS Core Banking API |
| BIAN.org | Chuẩn kiến trúc | Banking Industry Architecture Network |
| Designing Data-Intensive Applications | Sách (Martin Kleppmann) | Nền tảng hệ thống phân tán |
Chúc mừng bạn đã hoàn thành chuỗi bài học! Bạn đã có đầy đủ nền tảng để bắt đầu hành trình trở thành một Core Banking Developer. Hãy nhớ: trong lĩnh vực này, sự cẩn thận và tư duy hệ thống còn quan trọng hơn tốc độ code.