Từ Monolith đến Modern Core Banking

Các hệ thống Core Banking truyền thống (T24, Flexcube) được xây dựng theo kiến trúc Monolithic — toàn bộ nghiệp vụ (CIF, CASA, Lending, GL, Payments…) chạy trong một ứng dụng khổng lồ. Điều này gây ra:

  • Deploy rủi ro cao: Sửa một module nhỏ phải deploy lại toàn bộ hệ thống.
  • Scale không hiệu quả: Không thể scale riêng module Payments đang bị tải cao mà không scale cả hệ thống.
  • Technology lock-in: Bị buộc chặt vào một ngôn ngữ, một database duy nhất.

Xu hướng hiện nay là chuyển sang Headless Core Banking — tách rời domain logic khỏi kênh giao tiếp (Mobile App, Internet Banking, ATM).


Kiến trúc tổng thể

                    ┌─────────────────────────────────────┐
  CHANNELS          │  Mobile App  │  Internet Banking  │  ATM/POS  │
                    └──────────────────────┬──────────────────────────┘
                                           │ REST/gRPC
                    ┌──────────────────────▼──────────────────────────┐
  API GATEWAY       │         API Gateway (Auth, Rate Limit, Routing)  │
                    └──────────────────────┬──────────────────────────┘
                                           │
         ┌─────────────────────────────────┼──────────────────────────┐
         │                                 │                          │
  ┌──────▼──────┐               ┌──────────▼─────────┐    ┌──────────▼──────────┐
  │  CIF Service │               │  Account Service   │    │  Payment Service    │
  │  (Customer)  │               │  (CASA, GL)        │    │  (Transfers, Fees)  │
  └─────────────┘               └────────────────────┘    └─────────────────────┘
         │                                 │                          │
         └─────────────────────────────────┼──────────────────────────┘
                                           │ Events (Kafka/Dapr)
                    ┌──────────────────────▼──────────────────────────┐
  EVENT BUS         │              Message Broker (Kafka / Redis)      │
                    └──────────────────────┬──────────────────────────┘
                                           │
         ┌─────────────────────────────────┼──────────────────────────┐
         │                                 │                          │
  ┌──────▼──────┐               ┌──────────▼─────────┐    ┌──────────▼──────────┐
  │  Loan Service│               │  Notification Svc  │    │  Reporting Service  │
  │  (Lending)   │               │  (SMS, Push, Email)│    │  (CQRS Read Side)   │
  └─────────────┘               └────────────────────┘    └─────────────────────┘

Pattern 1: Event Sourcing cho Sổ Cái (Ledger)

Trong kiến trúc truyền thống, chúng ta lưu trạng thái hiện tại (current state). Trong Event Sourcing, chúng ta lưu chuỗi sự kiện (immutable events) tạo ra trạng thái đó.

Tại sao Event Sourcing phù hợp với Core Banking?

Ledger đã vốn là Event Sourcing rồi — mỗi bút toán là một event không thể thay đổi. Số dư hiện tại là kết quả replay tất cả các bút toán từ đầu.

// Các event trong Account domain
type AccountOpened struct {
    AccountID    string
    CIFNumber    string
    Currency     string
    OpenedAt     time.Time
}

type MoneyDeposited struct {
    AccountID     string
    Amount        int64
    TransactionID string
    OccurredAt    time.Time
}

type MoneyWithdrawn struct {
    AccountID     string
    Amount        int64
    TransactionID string
    OccurredAt    time.Time
}

// Tính số dư bằng cách replay events
func calculateBalance(events []Event) int64 {
    var balance int64
    for _, event := range events {
        switch e := event.(type) {
        case MoneyDeposited:
            balance += e.Amount
        case MoneyWithdrawn:
            balance -= e.Amount
        }
    }
    return balance
}

Pattern 2: CQRS — Command Query Responsibility Segregation

Core Banking có đặc thù: ghi phải cực kỳ chắc chắn (ACID) nhưng đọc cần cực kỳ nhanh (dashboard, báo cáo). CQRS tách hai luồng này hoàn toàn:

WRITE SIDE (Command)                READ SIDE (Query)
────────────────────────            ──────────────────────────
POST /transfers            →        Materialized Views
POST /accounts             →        Elasticsearch Index
PUT /loans/repay           →        Redis Cache

↓ Event Published ↓                ↑ Subscribe & Update ↑
         └──────────────────────────┘
              (Event Bus / Kafka)

Ví dụ thực tế:

  • Write Side: Xử lý lệnh chuyển tiền với PostgreSQL + full ACID, đảm bảo tiền không sai.
  • Read Side: Dashboard hiển thị lịch sử giao dịch từ Elasticsearch — truy vấn cực nhanh, full-text search, filter theo nhiều điều kiện.

Pattern 3: Saga — Giao dịch Phân tán qua nhiều Service

Khi chuyển tiền liên ngân hàng cần phối hợp 3 service: Account Service (trừ tiền), Payment Service (gửi lệnh NAPAS), Notification Service (báo SMS), làm sao đảm bảo tính toàn vẹn?

Choreography Saga (Hướng sự kiện)

Account Service                Payment Service           Notification Service
      │                               │                          │
      │── TransferInitiated ──────────▶│                          │
      │                               │── PaymentSubmitted ──────▶│
      │                               │                          │── SMS Sent
      │◀── PaymentCompleted ──────────│                          │
      │                               │                          │
   (release hold)                                            (done)

Nếu Payment thất bại:
      │◀── PaymentFailed ─────────────│
      │                               │
   (cancel hold, hoàn tiền)

Outbox Pattern — Đảm bảo Event không bao giờ bị mất

Vấn đề: Nếu service commit database thành công nhưng publish event lên Kafka thất bại thì sao?

Giải pháp: Ghi event vào database trong cùng transaction, sau đó có worker riêng đọc và publish lên Kafka.

-- Bảng outbox: ghi cùng transaction với business data
CREATE TABLE outbox_events (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    topic       VARCHAR(100) NOT NULL,  -- 'account.transfer.completed'
    payload     JSONB        NOT NULL,
    status      VARCHAR(20)  NOT NULL DEFAULT 'PENDING',
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    published_at TIMESTAMPTZ
);

-- Trong cùng một Database Transaction:
-- 1. Cập nhật số dư tài khoản
-- 2. Ghi ledger entries  
-- 3. INSERT vào outbox_events

-- Worker riêng chạy định kỳ:
-- SELECT * FROM outbox_events WHERE status = 'PENDING'
-- → Publish lên Kafka
-- → UPDATE status = 'PUBLISHED'

Thiết kế API cho giao dịch tài chính

Nguyên tắc thiết kế

  1. Stateless API: Mỗi request phải tự chứa đủ thông tin.
  2. Idempotency header bắt buộc cho mọi API thay đổi trạng thái.
  3. Phân tách rõ Request (lệnh) và Status (kiểm tra trạng thái).
POST /v1/transfers                    → Khởi tạo lệnh chuyển tiền
  Header: Idempotency-Key: <uuid>
  Body: { from, to, amount, currency }
  Response: { transfer_id, status: "PROCESSING" }

GET  /v1/transfers/{transfer_id}      → Kiểm tra kết quả
  Response: { status: "COMPLETED" | "FAILED", ... }

Không bao giờ thiết kế API chuyển tiền theo kiểu synchronous block vì việc xử lý qua NAPAS/SWIFT có thể mất vài giây đến vài phút.


Lựa chọn Stack Kỹ thuật

TầngLựa chọn phổ biếnLý do
Service FrameworkGo (Kratos, Fiber), Java (Spring Boot)Hiệu năng cao, type-safe
Database chínhPostgreSQLACID mạnh, JSONB flexible
CacheRedisSố dư, session, rate limiting
Event BusApache Kafka, Dapr PubSubDurable, ordered, replay
Service MeshIstio, DaprmTLS, circuit breaker
OrchestrationKubernetesAuto-scaling, self-healing

Tiếp theo, chúng ta sẽ hiểu cách Core Banking giao tiếp với thế giới bên ngoài — các chuẩn quốc tế mà mọi hệ thống tài chính đều phải nói cùng một “ngôn ngữ”. Đọc tiếp Phần 5 — Chuẩn Tích hợp Quốc tế: ISO 8583 & ISO 20022.