← Chương trước: Tổng quan vai trò Core Banking Developer | Mục lục Series | Chương tiếp theo: Phần 2 — Nghiệp vụ Ngân hàng Lõi: CIF, CASA & Lending →
Answer-first: Nguyên lý kế toán kép (Double-Entry Bookkeeping) bắt buộc mọi giao dịch tài chính phải ghi ít nhất hai bút toán Nợ (Debit) và Có (Credit) với tổng giá trị bằng nhau. Sổ cái (General Ledger) là bảng bất biến (append-only), lưu trữ số tiền dưới dạng số nguyên (BIGINT) và cấm tuyệt đối thao tác UPDATE/DELETE.
Tại sao một developer cần học kế toán?
Hầu hết developer khi nghe “kế toán” đều nghĩ đây là công việc của phòng tài vụ. Nhưng trong Core Banking, đặc biệt trong môi trường các hệ thống sổ cái phi tập trung (distributed ledgers) và kinh tế token (tokenized economy) năm 2026, kế toán kép chính là business logic cốt lõi nhất mà code của bạn phải thực thi, dù là tiền pháp định hay tài sản số. Nếu code của bạn sai, sổ cái mất cân bằng, ngân hàng không thể báo cáo lên Ngân hàng Nhà nước và hậu quả pháp lý là vô cùng nghiêm trọng.
Nguyên lý Kế toán Kép (Double-Entry Bookkeeping)
Được phát minh từ thế kỷ 15 bởi nhà toán học người Ý Luca Pacioli, nguyên lý này chỉ có một quy tắc duy nhất:
Mọi giao dịch tài chính đều phải được ghi chép vào ít nhất hai tài khoản, một bên Nợ (Debit) và một bên Có (Credit), với tổng giá trị hai bên phải bằng nhau.
Ví dụ thực tế: Khách hàng A chuyển 1.000.000 VNĐ cho khách hàng B
Trong tư duy thông thường, developer hay nghĩ đơn giản:
account_A.balance -= 1_000_000
account_B.balance += 1_000_000
Đây là sai về mặt kế toán. Cách đúng là ghi vào bảng General Ledger (GL):
| ID | Tài khoản (Account) | Loại bút toán | Số tiền |
|---|---|---|---|
| TX1 | Tài khoản A | Nợ (Debit) | 1.000.000 |
| TX1 | Tài khoản B | Có (Credit) | 1.000.000 |
Tổng Nợ = Tổng Có = 1.000.000 → Sổ cái cân bằng ✅
Bảng General Ledger (GL) — Trái tim của Core Banking
Toàn bộ hệ thống Core Banking thực ra chỉ xoay quanh việc ghi dữ liệu vào bảng GL một cách chính xác. Đây là thiết kế cơ bản nhất của bảng GL:
CREATE TABLE ledger_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
transaction_id UUID NOT NULL, -- Nhóm các bút toán cùng giao dịch
account_id UUID NOT NULL, -- Tài khoản nào bị ảnh hưởng
entry_type VARCHAR(6) NOT NULL, -- 'DEBIT' hoặc 'CREDIT'
amount BIGINT NOT NULL, -- Lưu theo đơn vị nhỏ nhất (VD: đồng, cents)
currency CHAR(3) NOT NULL, -- 'VND', 'USD', 'JPY'
balance_after BIGINT NOT NULL, -- Số dư sau bút toán này
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
description TEXT,
CONSTRAINT chk_amount_positive CHECK (amount > 0),
CONSTRAINT chk_entry_type CHECK (entry_type IN ('DEBIT', 'CREDIT'))
);
Lưu ý quan trọng: Luôn lưu tiền theo đơn vị nguyên (integer) — đồng, cents, satoshi. Không bao giờ dùng
FLOAThayDOUBLEđể lưu tiền vì lỗi làm tròn số thực (floating-point precision error) sẽ làm sổ cái mất cân bằng sau hàng nghìn giao dịch.
Kiểm tra sức khoẻ sổ cái: The Balance Invariant
Đây là câu query bạn phải có khả năng chạy bất kỳ lúc nào để xác nhận sổ cái không bị hỏng:
-- Tổng tất cả bút toán Nợ PHẢI LUÔN bằng tổng tất cả bút toán Có
SELECT
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;
-- Kết quả mong đợi: imbalance = 0
Nếu imbalance ≠ 0, có nghĩa là code của bạn đã ghi thiếu một bút toán ở đâu đó — đây là lỗi nghiêm trọng nhất trong Core Banking.
Cấu trúc tài khoản trong ngân hàng
Không phải tài khoản nào cũng là tài khoản của khách hàng. Trong ngân hàng, có nhiều loại tài khoản nội bộ:
| Loại tài khoản | Ý nghĩa | Ví dụ |
|---|---|---|
| Asset (Tài sản) | Tiền ngân hàng đang nắm giữ | Tiền mặt tại quỹ, Cho vay KH |
| Liability (Nợ phải trả) | Tiền ngân hàng nợ khách hàng | Số dư tài khoản của KH |
| Income (Thu nhập) | Doanh thu của ngân hàng | Phí giao dịch, lãi vay thu |
| Expense (Chi phí) | Chi phí của ngân hàng | Lãi tiết kiệm phải trả |
| Equity (Vốn chủ sở hữu) | Vốn của cổ đông | Vốn điều lệ |
Khi khách hàng gửi 10 triệu vào tài khoản tiết kiệm, hệ thống phải ghi:
- Nợ Tài khoản Tiền Mặt (Asset tăng lên)
- Có Tài khoản Tiết Kiệm Khách Hàng (Liability tăng lên — ngân hàng đang nợ KH)
Bài học cốt lõi
- Không bao giờ chỉ cập nhật số dư trực tiếp (
UPDATE accounts SET balance = balance - X). Luôn ghi bút toán vào ledger, rồi tính số dư từ ledger. - Mỗi giao dịch là một đơn vị nguyên tử — tất cả các bút toán phải cùng thành công hoặc cùng thất bại (Database Transaction).
- Ledger là immutable — không bao giờ UPDATE hay DELETE một bút toán đã ghi. Để sửa lỗi, bạn ghi thêm bút toán đảo chiều (reversal entry).
← Chương trước: Tổng quan vai trò Core Banking Developer | Mục lục Series | Chương tiếp theo: Phần 2 — Nghiệp vụ Ngân hàng Lõi: CIF, CASA & Lending →
❓ Câu Hỏi Thường Gặp (FAQ)
Q1: Tư duy Kế toán Kép & Sổ Cái (Ledger Foundation) giải quyết vấn đề cốt lõi nào trong kiến trúc hệ thống?
Nắm vững kế toán kép trong Core Banking: quy tắc Nợ/Có (debit/credit), tài khoản chữ T, và ràng buộc cân bằng bắt buộc của mọi hệ thống tài chính.
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.
