← Chương trước: Phần 6 — Bảo mật Core Banking, PCI-DSS & Dấu vết Kiểm toán | Mục lục Series | Chương tiếp theo: Phần 8 — Viết PRD cho Core Banking →
Answer-first: Hướng dẫn thực hành từng bước tự xây dựng dịch vụ Mini Core Banking hoàn chỉnh bằng Go và PostgreSQL: thiết kế database schema 6 bảng (CIF, CASA, Ledger, Transactions, Outbox, Audit Log), triển khai logic chuyển tiền ACID bất biến và kiểm thử tải đồng thời với k6.
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. Đặc biệt trong môi trường kỹ thuật năm 2026, các framework hiện đại đã tự động hóa phần lớn việc quản lý state và event streaming.
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ương trước: Phần 6 — Bảo mật Core Banking, PCI-DSS & Dấu vết Kiểm toán | Mục lục Series | Chương tiếp theo: Phần 8 — Viết PRD cho Core Banking →
❓ Câu Hỏi Thường Gặp (FAQ)
Q1: Tự xây dựng Hệ thống Mini Core Banking bằng Go giải quyết vấn đề cốt lõi nào trong kiến trúc hệ thống?
Xây dựng hệ thống mini Core Banking hoàn chỉnh bằng Go: sổ cái kế toán kép, ACID transactions, tài khoản CASA, quản lý khoản vay, và REST API — từ đầu đến cuối.
Q2: Những lưu ý quan trọng nhất khi triển khai thực tế là gì?
Cần chú trọng phân tầng ranh giới trách nhiệm (bounded context), thiết lập cơ chế fallback dự phòng, và giám sát chặt chẽ qua metrics OpenTelemetry để phát hiện sớm các điểm nghẽn.
Q3: Làm sao để kiểm thử và đánh giá hiệu quả sau khi áp dụng?
Áp dụng kiểm thử tải (load test), benchmark độ trễ P95/P99 trước và sau triển khai, kết hợp tracing phân tán để xác minh tính ổn định dưới tải cao.
