← 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:

  1. Thêm Lending module: Tạo khoản vay, tính lãi tự động, thu nợ định kỳ.
  2. 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.
  3. CQRS Read Side: Thêm Elasticsearch để tìm kiếm lịch sử giao dịch cực nhanh.
  4. Rate Limiting & Fraud Detection: Phát hiện giao dịch bất thường theo Rule Engine.
  5. 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ênLoạiNội dung
Apache FineractOpen SourceCore Banking đầy đủ cho tài chính vi mô
jPOS ProjectFramework + SáchISO 8583, payment switch
Ledger.io BlogBlog kỹ thuậtDouble-entry accounting code
Thought Machine BlogBlog kiến trúcVault Core — Core Banking hiện đại
Mambu Dev PortalDocsSaaS Core Banking API
BIAN.orgChuẩn kiến trúcBanking Industry Architecture Network
Designing Data-Intensive ApplicationsSá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.