AiChiaBill · Architecture Docs
☕ Java 21 · Spring Boot 3.4 · PostgreSQL 15 · Flyway

Tài Liệu Kỹ Thuật: CSDL, ORM & Kiến Trúc Java

Đặc tả kỹ thuật toàn diện về mô hình CSDL quan hệ PostgreSQL, chuẩn mapping Hibernate/JPA chống N+1, kiến trúc Spring Boot phân tầng dùng chung Service giữa SSR và REST API, 4 mẫu thiết kế GoF và 5 luồng nghiệp vụ tài chính cốt lõi của AiChiaBill.

104 Files
Java Core Architecture
controller · service · model · repo
10 Bảng
PostgreSQL Database Schema
Flyway Migrations V1–V11
100% Shared
Service Layer Consistency
SSR Thymeleaf & REST /api/v1
0 đ Sai Số
Kế Toán Chuẩn BigDecimal
Greedy Graph Debt Reduction

Sơ Đồ Thực Thể Quan Hệ (Entity-Relationship Diagram)

Sơ đồ CSDL PostgreSQL thực tế của hệ thống, chuẩn hóa qua 11 file migration Flyway (src/main/resources/db/migration/).

📊 PostgreSQL Database Relational Graph PostgreSQL 15 · Flyway V1-V11
erDiagram users ||--o{ groups : "creates (created_by)" users ||--o{ group_members : "joins" groups ||--o{ group_members : "contains" groups ||--o{ expenses : "tracks" users ||--o{ expenses : "pays (paid_by_user_id)" users ||--o{ expenses : "creates (created_by)" expenses ||--o{ expense_shares : "splits into" users ||--o{ expense_shares : "owes (user_id)" groups ||--o{ settlements : "requires" users ||--o{ settlements : "owes (debtor_id)" users ||--o{ settlements : "receives (creditor_id)" settlements ||--o{ payment_attempts : "tracks attempts" users ||--o{ app_notifications : "receives" users ||--o{ otp_codes : "requests" expenses ||--o{ expense_items : "itemizes (BY_ITEM)" expense_items ||--o{ expense_item_shares : "splits into" users ||--o{ expense_item_shares : "owes (user_id)" users { uuid id PK varchar email UK varchar password_hash varchar display_name varchar status "ACTIVE, PENDING_VERIFICATION" varchar phone varchar bank_name varchar bank_account_number varchar qr_image_path timestamptz created_at } groups { uuid id PK varchar name varchar description varchar invite_code UK varchar currency "VND" uuid created_by FK timestamptz created_at } group_members { uuid id PK uuid group_id FK uuid user_id FK varchar role "OWNER, ADMIN, MEMBER" varchar status "ACTIVE, LEFT" timestamptz joined_at } expenses { uuid id PK uuid group_id FK uuid paid_by_user_id FK decimal amount "CHECK amount > 0" varchar split_type "EQUAL, EXACT, PERCENTAGE, BY_ITEM" varchar category varchar status "ACTIVE, CANCELLED" bigint version "Optimistic Lock" timestamptz occurred_at } expense_shares { uuid id PK uuid expense_id FK uuid user_id FK decimal share_amount "CHECK share_amount >= 0" decimal percentage } settlements { uuid id PK uuid group_id FK uuid debtor_id FK uuid creditor_id FK decimal amount "CHECK amount > 0" varchar status "PENDING, MARKED_PAID, CONFIRMED" bigint version "Optimistic Lock" varchar provider_reference UK timestamptz confirmed_at }

Chi Tiết Các Bảng Dữ Liệu Cốt Lõi

Đặc tả kiểu dữ liệu, khóa chính, ràng buộc toàn vẹn và chiến lược đánh chỉ mục (Index).

users AUTH & IDENTITY
CộtKiểuRàng buộc / Ghi chú
idUUIDPK · Sinh tự động ở tầng Java
emailVARCHAR(120)UNIQUE · Định danh đăng nhập
password_hashVARCHAR(255)BCrypt hash (cost factor 10)
display_nameVARCHAR(100)Tên hiển thị công khai
statusVARCHAR(30)ACTIVE, PENDING_VERIFICATION, BANNED
bank_nameVARCHAR(100)Ngân hàng thụ hưởng VietQR (V9)
bank_account_numberVARCHAR(50)Số tài khoản nhận tiền (V9)
created_atTIMESTAMPTZThời điểm tạo tài khoản
groups & group_members MULTI-TENANT ISOLATION
CộtKiểuRàng buộc / Ghi chú
groups.idUUIDPK nhóm chi tiêu
groups.invite_codeVARCHAR(20)UNIQUE · Mã mời tham gia nhóm 6 ký tự
groups.created_byUUIDFK -> users(id) · Người lập nhóm
group_members.idUUIDPK quan hệ thành viên
group_id, user_idUUIDFK -> groups(id), users(id) · UNIQUE đôi
roleVARCHAR(20)OWNER, ADMIN, MEMBER
statusVARCHAR(20)ACTIVE, INVITED, LEFT
expenses & expense_shares FINANCIAL LEDGER
CộtKiểuRàng buộc / Ghi chú
expenses.idUUIDPK khoản chi
group_idUUIDFK -> groups(id)
paid_by_user_idUUIDFK -> users(id) · Người ứng tiền trước
amountDECIMAL(19,2)CHECK (amount > 0)
split_typeVARCHAR(20)EQUAL, EXACT, PERCENTAGE, BY_ITEM
versionBIGINTOptimistic locking chống ghi đè đồng thời
expense_shares.idUUIDPK phần chia nợ của từng thành viên
share_amountDECIMAL(19,2)CHECK (share_amount >= 0)
settlements & payment_attempts DEBT SETTLEMENT & 2-PHASE
CộtKiểuRàng buộc / Ghi chú
settlements.idUUIDPK giao dịch quyết toán
debtor_id, creditor_idUUIDFK -> users(id) · CHECK (debtor_id != creditor_id)
amountDECIMAL(19,2)CHECK (amount > 0)
statusVARCHAR(20)PENDING, MARKED_PAID, CONFIRMED
versionBIGINTOptimistic locking chống 2 người bấm cùng lúc
provider_referenceVARCHAR(80)UNIQUE · Mã tham chiếu giao dịch cổng TT
payment_attempts.idUUIDPK lưu vết từng lượt bấm thanh toán

Chiến Lược Đánh Chỉ Mục (PostgreSQL Index Strategy)

Đảm bảo truy vấn thời gian thực dưới 15ms ngay cả khi nhóm có hàng ngàn khoản chi tiêu.

db/migration/V1__init_schema.sql & V10__indexes.sql
-- 1. Index cho dòng thời gian khoản chi (Group Timeline Query)
CREATE INDEX idx_expenses_group_occurred ON expenses (group_id, occurred_at DESC);

-- 2. Index cho tính toán số dư cá nhân trên Dashboard (Dashboard Aggregation)
CREATE INDEX idx_expense_shares_user ON expense_shares (user_id, expense_id);

-- 3. Index cho tìm kiếm & kiểm tra nợ chéo giữa 2 thành viên
CREATE INDEX idx_settlements_debtor_creditor ON settlements (debtor_id, creditor_id, status);

-- 4. Partial Index cho chuông thông báo chưa đọc (Unread Notifications)
CREATE INDEX idx_notifications_recipient_unread ON app_notifications (recipient_id, created_at DESC) WHERE is_read = FALSE;

Quy Chuẩn Mapping JPA / Hibernate trong Java

Các quy ước mapping thực tế dùng trong package com.aichiabill.model, triệt tiêu lỗi N+1 và đảm bảo an toàn giao dịch đồng thời.

🔑
1. UUID Generation
Toàn bộ Entity dùng UUID sinh ở tầng Java, không dùng Long auto-increment tránh đoán trước ID và chống tấn công duyệt ID hàng loạt (ID Enumeration).
🛡️
2. Optimistic Locking (@Version)
Expense và Settlement có trường @Version Long version. Tránh hoàn toàn việc hai người cùng xác nhận hoặc cập nhật nợ gây xung đột dữ liệu.
⚡
3. 100% FetchType.LAZY
Toàn bộ @ManyToOne đều đặt fetch = FetchType.LAZY. Không một quan hệ nào dùng EAGER, triệt tiêu hoàn toàn nguy cơ nạp thừa dữ liệu.
🚫
4. Zero Cascade Discipline
Không dùng CascadeType.ALL hay orphanRemoval bừa bãi. Việc tạo/xóa đối tượng con được quản lý minh bạch qua Repository chuyên biệt.

Minh Họa Code Thực Tế: Entity & Repository

com/aichiabill/model/Expense.java
@Entity
@Table(name = "expenses")
@Getter @Setter @NoArgsConstructor
public class Expense {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "group_id", nullable = false)
    private ExpenseGroup group;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "paid_by_user_id", nullable = false)
    private User paidBy;

    @Column(nullable = false, precision = 19, scale = 2)
    private BigDecimal amount;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 20)
    private SplitType splitType;

    @Version
    private Long version = 0L; // Optimistic Locking

    @PrePersist
    protected void onCreate() {
        this.createdAt = Instant.now();
        this.updatedAt = Instant.now();
    }
}
com/aichiabill/repository/SettlementRepository.java (Giải pháp triệt tiêu N+1 Query)
public interface SettlementRepository extends JpaRepository<Settlement, UUID> {

    /**
     * @EntityGraph chỉ thị cho Hibernate JOIN FETCH người nợ (debtor), người nhận (creditor)
     * và nhóm (group) chỉ trong 1 câu SQL duy nhất, loại bỏ hoàn toàn bài toán N+1 Query.
     */
    @EntityGraph(attributePaths = {"debtor", "creditor", "group"})
    Page<Settlement> findByGroupIdOrderByCreatedAtDesc(UUID groupId, Pageable pageable);

    @EntityGraph(attributePaths = {"debtor", "creditor"})
    List<Settlement> findByGroupIdAndStatusIn(UUID groupId, Collection<SettlementStatus> statuses);
}

Kiến Trúc Phân Tầng Spring Boot 3.4 (Java 21)

Cấu trúc 4 lớp kinh điển với nguyên lý cốt lõi: Giao diện SSR Thymeleaf và REST API dùng chung 100% lớp Service nghiệp vụ.

🏗️ Spring MVC 4-Tier Layer Architecture
flowchart TD Client["Browser / Fetch Client / Swagger UI"] subgraph PresentationLayer ["1. Presentation Layer"] SSR["controller/* (9 Controllers)
Thymeleaf SSR Templates"] REST["api/* (5 @RestControllers)
JSON ApiResponse / ProblemDetail"] end subgraph ServiceLayer ["2. Business Logic Layer"] Service["service/* (22 Services)
ExpenseService · SettlementService · OtpService"] Strategy["service/strategy/*
SplitStrategy Interface & Factory"] end subgraph PersistenceLayer ["3. Data Access Layer"] Repo["repository/* (10 Spring Data JPA Interfaces)
@EntityGraph · Batch Queries"] end subgraph DatabaseLayer ["4. Storage & Cache Layer"] Postgres[("PostgreSQL 15 Database")] Redis[("Redis 7 Cache")] end Client -->|HTTP GET/POST| SSR Client -->|REST JSON /api/v1| REST SSR -->|Gọi nghiệp vụ| Service REST -->|Gọi nghiệp vụ| Service Service --> Strategy Service --> Repo Service -->|Cache Aside| Redis Repo -->|JPA / SQL| Postgres
🔗
Shared Service Pattern
ExpenseController và ExpenseApiController gọi chung cùng instance ExpenseService. Mọi business rules, kiểm tra quyền hạn, xác thực chỉ viết một lần duy nhất, không có nguy cơ lệch pha giữa Web và API.
📦
Tách Biệt DTO Biên Rõ Ràng
dto/: Dùng cho SSR form binding (linh hoạt, pre-fill qua session).
dto/api/: Immutable Java Record có kiểm tra chặt chẽ qua Jakarta Validation (@Valid, @NotBlank, @Positive).
💉
100% Constructor Injection
Toàn bộ 104 file Java sử dụng @RequiredArgsConstructor của Lombok cùng các trường private final. Tuyệt đối không dùng @Autowired trên field, đảm bảo code dễ viết Unit Test và bất biến.

Mẫu Thiết Kế GoF & Thuật Toán Tối Ưu Nợ

Ứng dụng các mẫu thiết kế chuẩn công nghiệp để giữ cho hệ thống mở rộng linh hoạt và thuật toán kế toán chính xác tuyệt đối.

1. Strategy Pattern: 4 Chiến Lược Chia Tiền

Đóng gói thuật toán tính toán và phân bổ phần dư từng cent cho 4 chế độ chia tiền linh hoạt.

com/aichiabill/service/strategy/EqualSplitStrategy.java (Chia đều & rải phần dư cent)
@Component
public class EqualSplitStrategy implements SplitStrategy {

    @Override
    public List<ExpenseShare> calculateShares(Expense expense, ExpenseForm form, Map<UUID, User> members) {
        BigDecimal total = expense.getAmount();
        int count = form.participantIds().size();
        
        // 1. Chia đều cơ sở, làm tròn xuống (FLOOR)
        BigDecimal baseShare = total.divide(BigDecimal.valueOf(count), 2, RoundingMode.FLOOR);
        
        // 2. Tính toán phần dư còn lại (VD: 100,000 / 3 = 33,333 -> dư 1 đồng)
        BigDecimal remainder = total.subtract(baseShare.multiply(BigDecimal.valueOf(count)));
        
        // 3. Rải từng cent/đồng cho các thành viên đầu tiên để tổng shares luôn = total 100%
        return CalculatorUtils.distributeRemainderCents(expense, form.participantIds(), baseShare, remainder);
    }
}

2. Thuật Toán Tối Ưu Hóa Nợ (Greedy Debt Simplification Graph)

Giảm thiểu tối đa số lần chuyển khoản giữa N thành viên từ O(N²) xuống tối đa N - 1 giao dịch.

com/aichiabill/service/SettlementOptimizer.java (Thuật toán tham lam khử nợ chéo)
public class SettlementOptimizer {

    /**
     * Thuật toán:
     * 1. Tính Net Balance của mỗi thành viên: Balance = Total_Paid - Total_Owed
     * 2. Phân loại thành 2 nhóm: Debtor (Balance < 0) và Creditor (Balance > 0)
     * 3. Ghép cặp Greedy: Lấy người nợ nhiều nhất ghép với người được nhận nhiều nhất
     *    -> Sau mỗi bước, ít nhất một bên được triệt tiêu công nợ hoàn toàn!
     */
    public static List<SettlementDraft> optimize(Map<UUID, BigDecimal> netBalances) {
        PriorityQueue<BalanceEntry> debtors = new PriorityQueue<>((a, b) -> a.amount.compareTo(b.amount)); // Min-heap
        PriorityQueue<BalanceEntry> creditors = new PriorityQueue<>((a, b) -> b.amount.compareTo(a.amount)); // Max-heap

        // Đưa vào hàng đợi ưu tiên...
        List<SettlementDraft> results = new ArrayList<>();
        while (!debtors.isEmpty() && !creditors.isEmpty()) {
            BalanceEntry debtor = debtors.poll();
            BalanceEntry creditor = creditors.poll();
            
            BigDecimal settleAmount = debtor.amount.abs().min(creditor.amount);
            results.add(new SettlementDraft(debtor.userId, creditor.userId, settleAmount));

            // Cập nhật số dư còn lại sau khi thanh toán từng phần
            BigDecimal remainingDebtor = debtor.amount.add(settleAmount);
            BigDecimal remainingCreditor = creditor.amount.subtract(settleAmount);
            
            if (remainingDebtor.compareTo(BigDecimal.ZERO) < 0) debtors.add(new BalanceEntry(debtor.userId, remainingDebtor));
            if (remainingCreditor.compareTo(BigDecimal.ZERO) > 0) creditors.add(new BalanceEntry(creditor.userId, remainingCreditor));
        }
        return results;
    }
}

3. Quy Trình Quyết Toán 2 Bước (Two-Phase Commit Protocol)

Đảm bảo chỉ khi chủ nợ bấm xác nhận nhận tiền thì số dư hiển thị mới được gạch nợ.

stateDiagram-v2 [*] --> PENDING : recalculateSettlements() PENDING --> MARKED_PAID : POST /settlements/{id}/pay (Debtor quét VietQR / Bấm chuyển tiền) MARKED_PAID --> CONFIRMED : POST /settlements/{id}/confirm (Creditor kiểm tra bank & xác nhận) PENDING --> CANCELLED : recalculateSettlements() tạo tính toán mới CONFIRMED --> [*] : Công nợ hoàn tất · Số dư cập nhật chính thức

5 Luồng Nghiệp Vụ Cốt Lõi (Sequence & Workflows)

Trình tự tương tác end-to-end giữa trình duyệt, Controller, Service, Repository, Database và các bên thứ ba.

1
Đăng Ký & Xác Thực OTP Qua Email (State Machine)
Người dùng đăng ký -> User được tạo với trạng thái PENDING_VERIFICATION -> OtpService sinh mã ngẫu nhiên 6 số, băm SHA-256 lưu DB kèm thời hạn 10 phút -> Gửi plaintext qua Gmail SMTP. Khi người dùng nhập OTP hợp lệ -> Kích hoạt tài khoản thành ACTIVE.
AuthController OtpService (SHA-256) EmailService Spring Security
2
Tạo Khoản Chi & Phân Bổ Nợ Theo Chiến Lược
ExpenseController.createExpense nhận dữ liệu form hoặc bản nháp quét hóa đơn AI OCR trong session -> Gọi ExpenseService kiểm tra quyền thành viên nhóm -> Lưu Expense -> Kích hoạt SplitStrategyFactory để chọn chiến lược chia tiền (EQUAL, EXACT, PERCENTAGE, BY_ITEM) -> Tính toán và lưu danh sách ExpenseShare -> Bắn thông báo real-time qua WebSocket.
ExpenseController ExpenseService SplitStrategy NotificationService
3
Quyết Toán & Tối Ưu Hóa Nợ Chéo (Settlement Optimization)
Chủ nhóm bấm "Tính toán lại" -> SettlementService lấy tất cả khoản chi chưa quyết toán -> Tính mảng số dư ròng -> Gọi SettlementOptimizer.optimize khử nợ chéo -> Sinh bộ giao dịch tối ưu -> Người nợ bấm trả tiền (kèm mã VietQR tạo tức thì) -> Người nhận xác nhận -> Số dư nợ được thanh toán dứt điểm.
SettlementService SettlementOptimizer VietQR Napas 24/7 Two-Phase Commit
4
Quét Hóa Đơn AI OCR: 2-Tier Fallback Pipeline
Người dùng tải ảnh hóa đơn -> Tier 1: Gửi ảnh tới OpenAI Vision API / Router OCR tương thích (timeout 60s) để bóc tách danh sách món ăn, giá tiền -> Nếu lỗi mạng/timeout hoặc file PDF -> Tier 2: Fallback sang Service OCR text thuần + Regex Line Parser bóc tách số tiền an toàn -> Lưu ExpenseDraft vào session để pre-fill màn hình tạo khoản chi.
HybridInvoiceVisionClient OpenAiVisionClient OcrApiClient ReceiptLineParser
5
Trung Tâm Thông Báo Thời Gian Thực (WebSocket STOMP)
Bất kỳ sự kiện nào (tạo khoản chi, mời thành viên, thanh toán nợ) kích hoạt NotificationService -> Lưu bản ghi vào bảng app_notifications -> Đẩy gói tin qua kênh STOMP cá nhân /topic/users/{id} (rung chuông và tăng badge counter trên thanh Topbar) và kênh nhóm /topic/groups/{groupId} để đồng bộ màn hình trực tiếp.
WebSocketConfig NotificationService STOMP Broker SockJS Fallback

Bảng Màu Chủ Đạo (Color Palette Tokens)

Bấm vào bất kỳ ô màu nào để sao chép mã HEX hoặc biến CSS trực tiếp vào bộ nhớ tạm.

#10B981
Emerald Green
Màu sắc thương hiệu cốt lõi, số dư dương, nút hành động chính (Primary CTA).
--accent-emeraldSao chép 📋
#34D399
Mint Light
Trạng thái xác thực thành công, laser scan glow, text highlight.
--accent-mintSao chép 📋
#6366F1
Fintech Indigo
Ma trận đồ thị nợ, liên kết thành viên, hub xử lý logic quyết toán.
--accent-indigoSao chép 📋
#06B6D4
Cyan Energy
Xung điện dữ liệu hóa đơn, AI OCR Scanner, ống dẫn luồng tiền.
--accent-cyanSao chép 📋

Thư Viện Linh Kiện Giao Diện (Component Library)

Các khối linh kiện tương tác cao cấp phục vụ trải nghiệm người dùng fintech mượt mà.

AI VISION SCANNER
Hóa đơn Lẩu Dê 404
Đang nhận diện món ăn...

Thư Viện Màn Hình & Mockups Hệ Thống

Bấm vào bất kỳ thẻ màn hình nào để mở chế độ xem phóng to chi tiết (Lightbox).

Dashboard
Màn Hình Tổng Quan (Dashboard)
Bento Grid 4 thẻ số dư + Biểu đồ chi tiêu 12 tháng + Banner AI OCR.
Groups
Danh Sách Nhóm (Groups Hub)
Lưới 3 cột x 3 dòng, phân trang mượt mà kèm lọc theo trạng thái.
Settlement
Quyết Toán & VietQR (Settlement)
Bảng khử nợ chéo tối ưu + Thẻ VietQR chuyển tiền tức thì.
Đã sao chép vào bộ nhớ tạm!