Files
sys-analysis-design/e-commerce/docs/sections/05-thiet-ke-du-lieu.md
2026-09-08 12:35:40 +07:00

49 KiB
Raw Blame History

section, title, status, version, reviewer_notes
section title status version reviewer_notes
05 Thiết kế dữ liệu approved 3

5. Thiết kế dữ liệu (Data & Database Design)

Đầu vào: docs/00-project-brief.md (profile: scale = large, hasPII = true, hasPayment = true, greenfield không có hệ thống cũ), 01-tong-quan.md (Glossary/entities mục 1.3), 02-phan-tich-yeu-cau.md (FR-01..FR-27, NFR-04/05/06), 03-kien-truc.md (kiến trúc database-per-service trên RDS PostgreSQL Multi-AZ, cache ElastiCache Redis, tìm kiếm OpenSearch như read-model phái sinh, lưu file lớn — ảnh sản phẩm/KYC — trên S3).

5.0 Nguyên tắc thiết kế

  • Database-per-service theo ranh giới đã chốt ở mục 3.1: mỗi service sở hữu schema/database riêng trên RDS PostgreSQL Multi-AZ; không có ràng buộc khoá ngoại (FK) vật lý xuyên service — các trường tham chiếu chéo service (VD seller_id trong Cart & Order Service trỏ tới seller.id của Seller Management Service) là FK logic, được đảm bảo nhất quán qua sự kiện (event) trên message broker (Kafka/MSK) theo mô hình saga/eventual consistency, không qua transaction DB phân tán.
  • Khoá chính: dùng UUID (sinh phía ứng dụng hoặc gen_random_uuid()) cho phần lớn bảng nghiệp vụ để tránh xung đột ID khi các service độc lập sinh dữ liệu và hỗ trợ replication/migration sau này. Riêng các bảng log khối lượng lớn, append-only (notification_log, shipment_event, audit_log) dùng BIGINT IDENTITY để tối ưu ghi tuần tự và partitioning theo thời gian.
  • Tên entity/bảng khớp Glossary mục 1.3 (Product, ProductVariant, Category, Cart, CartItem, Order, OrderItem, Payment, Shipment, ReturnRequest, Dispute, Promotion, Review, Notification, CommissionRule, Payout, KYCDocument, LoyaltyAccount, LoyaltyTransaction, MembershipTier, Wishlist, Currency, Language). Tên bảng SQL dùng snake_case số ít (VD product, order_item) — quy ước đặt tên kỹ thuật, không đổi nghĩa entity.
  • Đánh dấu dữ liệu nhạy cảm bằng nhãn [PII] (dữ liệu cá nhân — NĐ13/2023) và [Payment] (dữ liệu tài chính/thanh toán) ngay tại cột liên quan để security-architect rà soát mã hoá at-rest/in-transit, tokenization, và kiểm soát truy cập ở mục 8.
  • Không thiết kế API request/response — thuộc phạm vi api-designer (mục 4).
  • Do brief không cung cấp số liệu khối lượng/tăng trưởng cụ thể theo tháng/năm (chỉ có ước lượng bậc lớn ở mục brief: hàng trăm nghìn SKU, hàng trăm nghìn–hàng triệu user, hàng nghìn–chục nghìn concurrent), các quyết định partitioning/retention dưới đây dựa trên giả định thận trọng (xem assumptions), thiết kế đủ đơn giản để điều chỉnh khi có số liệu thực tế.

5.1 Mô hình dữ liệu tổng quan (ERD)

5.1.1 ERD logic toàn hệ thống (rút gọn quan hệ chính giữa các bounded context)

erDiagram
    CUSTOMER ||--o{ CUSTOMER_ADDRESS : has
    CUSTOMER ||--o{ OAUTH_IDENTITY : links
    CUSTOMER ||--o| LOYALTY_ACCOUNT : owns
    CUSTOMER ||--o{ WISHLIST_ITEM : saves
    CUSTOMER ||--o{ CART : owns
    CUSTOMER ||--o{ ORDER : places
    CUSTOMER ||--o{ REVIEW : writes
    CUSTOMER ||--o{ RETURN_REQUEST : requests
    CUSTOMER ||--o{ DISPUTE : raises

    LOYALTY_ACCOUNT ||--o{ LOYALTY_TRANSACTION : records
    LOYALTY_ACCOUNT }o--|| MEMBERSHIP_TIER : classified_as

    CART ||--o{ CART_ITEM : contains
    CART_ITEM }o--|| PRODUCT_VARIANT : references

    ORDER ||--o{ ORDER_SELLER : splits_into
    ORDER ||--o| PAYMENT : paid_by
    ORDER ||--o{ PROMOTION_USAGE : applies

    ORDER_SELLER ||--o{ ORDER_ITEM : contains
    ORDER_SELLER ||--o| SHIPMENT : fulfilled_by
    ORDER_SELLER ||--o{ RETURN_REQUEST : may_have
    ORDER_SELLER ||--o{ DISPUTE : may_have
    ORDER_SELLER ||--o| COMMISSION_TRANSACTION : generates
    ORDER_SELLER }o--|| SELLER : belongs_to
    ORDER_ITEM }o--|| PRODUCT_VARIANT : references

    PROMOTION ||--o{ PROMOTION_USAGE : used_in

    SELLER ||--o{ KYC_DOCUMENT : submits
    SELLER ||--o{ PRODUCT : lists
    SELLER ||--o| SELLER_BANK_ACCOUNT : has
    SELLER ||--o{ COMMISSION_TRANSACTION : accrues
    SELLER ||--o{ PAYOUT : receives
    PAYOUT ||--o{ PAYOUT_HOLD : contains

    PRODUCT ||--o{ PRODUCT_VARIANT : has
    PRODUCT }o--|| CATEGORY : classified_as
    CATEGORY ||--o| COMMISSION_RULE : rated_by
    PRODUCT_VARIANT ||--o| INVENTORY_STOCK : tracked_by
    PRODUCT ||--o{ REVIEW : receives
    PRODUCT ||--o{ WISHLIST_ITEM : saved_in

Ghi chú: đường nối trong ERD tổng quan thể hiện quan hệ logic nghiệp vụ, không phải FK vật lý (vì mỗi khối thực thể nằm ở database riêng của service tương ứng — xem 5.2). PlatformAdmin, OpsStaff, CSR không xuất hiện là entity dữ liệu riêng vì chỉ là vai trò (role) trong bảng user_account của Identity Service (5.2.1); Language, Currency là bảng cấu hình dùng chung, đặt tại 5.2.2. Bảng audit_log (Audit & Compliance Service, bổ sung v3 — xem 5.2.11) cũng không xuất hiện trong ERD tổng quan này vì đây là bảng ghi vết (audit trail) polymorphic tham chiếu tới nhiều loại resource khác nhau qua resource_type/resource_id chứ không phải quan hệ nghiệp vụ 1-1/1-n/n-n cố định với một entity duy nhất — xem ERD riêng tại 5.1.2.

5.1.2 ERD chi tiết theo bounded context

Identity & Access Service

erDiagram
    USER_ACCOUNT ||--o{ OAUTH_IDENTITY : links
    USER_ACCOUNT ||--o{ MFA_DEVICE : enrolls
    USER_ACCOUNT ||--o| CUSTOMER_PROFILE : extends
    USER_ACCOUNT ||--o{ CUSTOMER_ADDRESS : has

    USER_ACCOUNT {
        uuid id PK
        string email "PII"
        string phone "PII"
        string password_hash
        string role
        boolean mfa_enabled
        string status
        int failed_login_count
        timestamp locked_until
        timestamp last_failed_login_at
    }
    OAUTH_IDENTITY {
        uuid id PK
        uuid user_account_id FK
        string provider
        string provider_user_id
    }
    MFA_DEVICE {
        uuid id PK
        uuid user_account_id FK
        string method
        string secret_encrypted "PII"
    }
    CUSTOMER_PROFILE {
        uuid user_account_id PK, FK
        string full_name "PII"
        date date_of_birth "PII"
        string preferred_language
        string preferred_currency
    }
    CUSTOMER_ADDRESS {
        uuid id PK
        uuid user_account_id FK
        string recipient_name "PII"
        string phone "PII"
        string address_line "PII"
        boolean is_default
    }

Catalog & Inventory Service

erDiagram
    CATEGORY ||--o{ CATEGORY : parent_of
    CATEGORY ||--o{ CATEGORY_I18N : localized_as
    CATEGORY ||--o{ PRODUCT : classifies
    PRODUCT ||--o{ PRODUCT_I18N : localized_as
    PRODUCT ||--o{ PRODUCT_VARIANT : has
    PRODUCT_VARIANT ||--o| INVENTORY_STOCK : tracked_by
    PRODUCT ||--o{ WISHLIST_ITEM : saved_in
    LANGUAGE ||--o{ PRODUCT_I18N : used_by
    CURRENCY ||--o{ EXCHANGE_RATE : quoted_as

    CATEGORY {
        uuid id PK
        uuid parent_category_id FK
        string code
        boolean is_active
    }
    PRODUCT {
        uuid id PK
        uuid seller_id FK
        uuid category_id FK
        string status
    }
    PRODUCT_VARIANT {
        uuid id PK
        uuid product_id FK
        string sku_code
        numeric price_amount
        string currency_code
    }
    INVENTORY_STOCK {
        uuid variant_id PK, FK
        int quantity_available
        int quantity_reserved
    }
    WISHLIST_ITEM {
        uuid id PK
        uuid customer_id FK
        uuid product_id FK
    }
    LANGUAGE {
        string code PK
        string name
        boolean is_default
    }
    CURRENCY {
        string code PK
        string name
        boolean is_transactional
    }
    EXCHANGE_RATE {
        uuid id PK
        string currency_code FK
        numeric rate_to_vnd
        date effective_date
    }

Cart & Order Service

erDiagram
    CART ||--o{ CART_ITEM : contains
    ORDER ||--o{ ORDER_SELLER : splits_into
    ORDER_SELLER ||--o{ ORDER_ITEM : contains
    ORDER_SELLER ||--o{ RETURN_REQUEST : may_have
    ORDER_SELLER ||--o{ DISPUTE : may_have
    ORDER_SELLER ||--o{ ORDER_STATUS_HISTORY : tracks

    CART {
        uuid id PK
        uuid customer_id FK
        string session_id
        string status
    }
    CART_ITEM {
        uuid id PK
        uuid cart_id FK
        uuid product_variant_id FK
        uuid seller_id FK
        int quantity
    }
    ORDER {
        uuid id PK
        uuid customer_id FK
        string order_number
        numeric total_amount
        string status
    }
    ORDER_SELLER {
        uuid id PK
        uuid order_id FK
        uuid seller_id FK
        string sub_order_number
        string status
    }
    ORDER_ITEM {
        uuid id PK
        uuid order_seller_id FK
        uuid product_variant_id FK
        int quantity
        numeric unit_price
    }
    RETURN_REQUEST {
        uuid id PK
        uuid order_seller_id FK
        uuid customer_id FK
        string status
    }
    DISPUTE {
        uuid id PK
        uuid order_seller_id FK
        uuid assigned_csr_id FK
        string status
    }
    ORDER_STATUS_HISTORY {
        bigint id PK
        uuid order_seller_id FK
        string status
        timestamp changed_at
    }

Payment Service

erDiagram
    PAYMENT ||--o{ PAYMENT_RECONCILIATION_LOG : reconciled_by

    PAYMENT {
        uuid id PK
        uuid order_id FK
        string method
        numeric amount "Payment"
        string gateway_transaction_ref "Payment"
        string status
    }
    PAYMENT_RECONCILIATION_LOG {
        uuid id PK
        uuid payment_id FK
        string gateway_status
        timestamp reconciled_at
    }

Seller Management Service

erDiagram
    SELLER ||--o{ KYC_DOCUMENT : submits
    SELLER ||--o| SELLER_BANK_ACCOUNT : has

    SELLER {
        uuid id PK
        uuid user_account_id FK
        string business_name
        string tax_code "PII"
        string status
    }
    KYC_DOCUMENT {
        uuid id PK
        uuid seller_id FK
        string document_type
        string file_url_s3 "PII"
        string verified_status
    }
    SELLER_BANK_ACCOUNT {
        uuid id PK
        uuid seller_id FK
        string bank_name
        string account_number "PII, Payment"
        string account_holder_name "PII"
    }

Commission & Payout Service

erDiagram
    COMMISSION_RULE ||--o{ COMMISSION_TRANSACTION : applies_to
    COMMISSION_TRANSACTION }o--|| PAYOUT : settled_in
    PAYOUT ||--o{ PAYOUT_HOLD : contains

    COMMISSION_RULE {
        uuid id PK
        uuid category_id FK
        numeric commission_percent
        int hold_days
        date effective_from
    }
    COMMISSION_TRANSACTION {
        uuid id PK
        uuid order_seller_id FK
        uuid seller_id FK
        numeric commission_amount
        numeric net_amount
    }
    PAYOUT {
        uuid id PK
        uuid seller_id FK
        date period_start
        date period_end
        numeric total_net_amount "Payment"
        string bank_transfer_ref "Payment"
        string status
    }
    PAYOUT_HOLD {
        uuid id PK
        uuid commission_transaction_id FK
        date hold_until_date
        string release_status
    }

Promotion & Loyalty Service

erDiagram
    PROMOTION ||--o{ PROMOTION_USAGE : used_in
    LOYALTY_ACCOUNT ||--o{ LOYALTY_TRANSACTION : records
    LOYALTY_ACCOUNT }o--|| MEMBERSHIP_TIER : classified_as

    PROMOTION {
        uuid id PK
        string code
        string type
        numeric value
        string status
    }
    PROMOTION_USAGE {
        uuid id PK
        uuid promotion_id FK
        uuid order_id FK
        uuid customer_id FK
    }
    LOYALTY_ACCOUNT {
        uuid id PK
        uuid customer_id FK
        int points_balance
        numeric total_spend_12m
    }
    LOYALTY_TRANSACTION {
        uuid id PK
        uuid loyalty_account_id FK
        uuid order_id FK
        string type
        int points
    }
    MEMBERSHIP_TIER {
        uuid id PK
        string name
        numeric min_spend_threshold
    }

Review, Notification, Shipping & Fulfillment Service

erDiagram
    REVIEW {
        uuid id PK
        uuid product_id FK
        uuid customer_id FK
        uuid order_item_id FK
        int rating
        string status
    }
    NOTIFICATION_LOG {
        bigint id PK
        uuid recipient_user_id FK
        string channel
        string status
    }
    SHIPMENT ||--o{ SHIPMENT_EVENT : has
    SHIPMENT {
        uuid id PK
        uuid order_seller_id FK
        string carrier
        string tracking_number
        string status
    }
    SHIPMENT_EVENT {
        bigint id PK
        uuid shipment_id FK
        string event_status
        timestamp event_time
    }

Audit & Compliance Service (bổ sung v3 — theo findings bảo mật mục 8)

erDiagram
    AUDIT_LOG {
        bigint id PK
        uuid actor_id FK
        string actor_role
        string action
        string resource_type
        uuid resource_id
        jsonb before_json
        jsonb after_json
        string ip_address
        string user_agent
        timestamp created_at
    }

AUDIT_LOG không có quan hệ FK vật lý tới bất kỳ entity nào khác (kể cả actor_id) — tham chiếu là FK logic dạng polymorphic qua resource_type/resource_id, ghi nhận sự kiện phát sinh từ nhiều bounded context khác nhau (Seller Management, Commission & Payout, Cart & Order...). Chi tiết đặt vấn đề, cơ chế ghi và retention xem 5.2.11.

5.2 Database Schema chi tiết theo service

Quy ước cột chung không lặp lại ở từng bảng: created_at timestamptz DEFAULT now(), updated_at timestamptz (trigger cập nhật) có ở hầu hết bảng trừ log append-only. PK mặc định uuid DEFAULT gen_random_uuid() trừ khi ghi chú khác.

5.2.1 Identity & Access Service (phục vụ FR-01, FR-02, FR-03, FR-27)

Bảng user_account

Cột Kiểu dữ liệu PK/FK Constraint/Index Ghi chú
id uuid PK
email varchar(255) UNIQUE, NOT NULL, index [PII]
phone varchar(20) index [PII], nullable
password_hash varchar(255) NOT NULL bcrypt/argon2, nullable nếu chỉ dùng OAuth
role varchar(20) CHECK IN ('customer','seller','platform_admin','ops_staff','csr')
mfa_enabled boolean DEFAULT false FR-27; bắt buộc true khi role=platform_admin (kiểm tra ở tầng ứng dụng)
status varchar(20) CHECK IN ('active','locked','deactivated')
last_login_at timestamptz
failed_login_count int DEFAULT 0, CHECK >= 0 (v3 — theo review mục 8) đếm số lần đăng nhập sai liên tiếp; reset về 0 khi đăng nhập thành công
locked_until timestamptz nullable (v3) thời điểm tài khoản được tự động mở khoá sau khi bị khoá tạm do vượt ngưỡng failed_login_count (ngưỡng/khoảng thời gian khoá cụ thể do security-architect quy định ở mục 8); index (locked_until) hỗ trợ job quét mở khoá
last_failed_login_at timestamptz nullable (v3) thời điểm lần đăng nhập sai gần nhất, phục vụ giám sát brute-force

Bảng oauth_identity (FR-02) — id (PK), user_account_id (FK → user_account), provider (google/facebook), provider_user_id, linked_at. UNIQUE(provider, provider_user_id).

Bảng mfa_device (FR-27) — id (PK), user_account_id (FK), method (totp/sms), secret_encrypted [PII] (mã hoá bắt buộc), enabled, created_at.

Bảng customer_profile (FR-03) — user_account_id (PK, FK 1-1 → user_account), full_name [PII], date_of_birth [PII], gender, preferred_language (FK → language.code), preferred_currency (FK → currency.code).

Bảng customer_address (FR-03) — id (PK), user_account_id (FK), recipient_name [PII], phone [PII], address_line [PII], ward, district, province, country, is_default (boolean), created_at. Index (user_account_id, is_default).

5.2.2 Catalog & Inventory Service (phục vụ FR-04, FR-10, FR-15, FR-16, FR-18, FR-24)

Bảng category — id (PK), parent_category_id (FK self-reference, nullable), code (UNIQUE), commission_rule_id (FK logic → Commission Service commission_rule.id), is_active. Index (parent_category_id).

Bảng category_i18n (FR-15) — id (PK), category_id (FK), language_code (FK → language.code), name, description. UNIQUE(category_id, language_code).

Bảng product (FR-18, FR-24) — id (PK), seller_id (FK logic → Seller Management seller.id), category_id (FK), status (draft/active/hidden_by_admin/removed — cột phục vụ FR-24 quản trị catalog toàn sàn), created_at, updated_at. Index (seller_id), index (category_id, status) phục vụ FR-04 lọc theo ngành hàng.

Bảng product_i18n (FR-15) — id (PK), product_id (FK), language_code (FK), name, description (text). UNIQUE(product_id, language_code).

Bảng product_variant (FR-04, FR-18) — id (PK), product_id (FK), sku_code (UNIQUE), attributes (jsonb — VD size/màu), price_amount (numeric(14,2)), currency_code (FK → currency.code, mặc định VND), status. Index (sku_code).

Bảng inventory_stock (FR-18, FR-26) — variant_id (PK, FK 1-1 → product_variant), quantity_available (int, CHECK >= 0), quantity_reserved (int, CHECK >= 0), warehouse_location, updated_at. Index (quantity_available) hỗ trợ truy vấn còn hàng.

Bảng wishlist_item (FR-10) — id (PK), customer_id (FK logic → Identity user_account.id), product_id (FK), added_at. UNIQUE(customer_id, product_id).

Bảng language (FR-15) — code (PK, VD vi/en/zh/ko/ja), name, is_default (chỉ vi=true). Dữ liệu seed tĩnh, không tăng trưởng.

Bảng currency (FR-16) — code (PK, VD VND/USD/...), name, is_transactional (chỉ VND=true theo brief — không giao dịch trực tiếp ngoại tệ).

Bảng exchange_rate (FR-16) — id (PK), currency_code (FK), rate_to_vnd (numeric), effective_date (date). Chỉ phục vụ hiển thị quy đổi tham khảo, không dùng để thanh toán. Index (currency_code, effective_date DESC).

Ghi chú: dữ liệu tìm kiếm/lọc thời gian thực (FR-04) được phái sinh sang OpenSearch qua event ProductUpdated/ProductCreated từ service này (theo mục 3.2); OpenSearch không phải hệ quản trị CSDL giao dịch nên không đưa schema chi tiết vào đây — chỉ số hoá lại các trường trên.

5.2.3 Cart & Order Service (phục vụ FR-05, FR-06, FR-08, FR-09, FR-19, FR-25)

Bảng cart (FR-05) — id (PK), customer_id (FK logic, nullable — null nếu Guest), session_id (varchar, dùng cho Guest chưa đăng nhập), status (active/converted/abandoned), updated_at. Index (customer_id), index (session_id).

Bảng cart_item (FR-05) — id (PK), cart_id (FK), product_variant_id (FK logic), seller_id (FK logic, denormalized để hỗ trợ tách đơn ở FR-06), quantity (int, CHECK > 0), unit_price_snapshot (numeric), added_at. Index (cart_id).

Bảng order (FR-06, FR-08) — id (PK), customer_id (FK logic, nullable — Guest checkout), order_number (UNIQUE, human-readable), total_amount (numeric), currency_code (mặc định VND), status (pending_payment/confirmed/partially_fulfilled/completed/cancelled), promotion_id (FK logic, nullable), placed_at. Index (customer_id, placed_at DESC).

Bảng order_seller (FR-06, FR-19) — id (PK), order_id (FK), seller_id (FK logic), sub_order_number (UNIQUE), subtotal_amount (numeric), status (pending/confirmed/packed/shipped/delivered/cancelled/returned), created_at, updated_at. Index (seller_id, status) — truy vấn dashboard đơn hàng seller (FR-19).

Bảng order_item (FR-06) — id (PK), order_seller_id (FK), product_variant_id (FK logic), product_name_snapshot, quantity (int), unit_price (numeric), line_total (numeric). Index (order_seller_id).

Bảng order_status_history (FR-08) — id (bigint, PK, identity), order_seller_id (FK), status, changed_at, changed_by (user_account_id logic). Append-only, index (order_seller_id, changed_at).

Bảng return_request (FR-09) — id (PK), order_seller_id (FK), customer_id (FK logic), reason (text), status (requested/approved/rejected/refunded), requested_at, resolved_at.

Bảng dispute (FR-09, FR-25) — id (PK), order_seller_id (FK), raised_by (customer/seller), assigned_csr_id (FK logic → Identity user_account.id role=csr), status (open/investigating/resolved/escalated), created_at, resolved_at. Index (assigned_csr_id, status).

5.2.4 Payment Service (phục vụ FR-07)

Bảng payment — id (PK), order_id (FK logic → Cart & Order order.id), method (vnpay/momo/cod), amount (numeric(14,2)) [Payment], currency_code, gateway_transaction_ref (varchar) [Payment], status (pending/success/failed/refunded), raw_gateway_response (jsonb, chỉ lưu dữ liệu phản hồi phi thẻ — không lưu số thẻ/CVV theo NFR-05), paid_at. Index (order_id), index (gateway_transaction_ref) phục vụ đối soát.

Bảng payment_reconciliation_log — id (PK), payment_id (FK), gateway_status, discrepancy_note, reconciled_at. Append-only phục vụ job đối soát định kỳ (mục 3.4).

Không có bảng lưu thông tin thẻ thanh toán — đúng theo quyết định kiến trúc "PCI-DSS scope giảm" (mục 3.1): toàn bộ dữ liệu thẻ do VNPay/Momo xử lý, hệ thống chỉ lưu tham chiếu giao dịch.

5.2.5 Seller Management Service (phục vụ FR-17, FR-20, FR-23)

Bảng seller (FR-17, FR-23) — id (PK), user_account_id (FK logic → Identity user_account.id), business_name, tax_code [PII], business_license_number [PII], status (pending_kyc/active/suspended/rejected), approved_by (FK logic, admin), approved_at, created_at. Index (status) phục vụ FR-23 giám sát danh sách seller.

Bảng kyc_document (FR-17) — id (PK), seller_id (FK), document_type (business_license/id_card_front/id_card_back), file_url_s3 (varchar, trỏ tới object S3 riêng biệt theo mục 3.1) [PII], verified_status (pending/verified/rejected), reviewed_by (FK logic, admin), reviewed_at, uploaded_at.

Bảng seller_bank_account (FR-22, dữ liệu do FR-17 thu thập) — id (PK), seller_id (FK), bank_name, account_number [PII, Payment], account_holder_name [PII], is_active, updated_at.

5.2.6 Commission & Payout Service (phục vụ FR-20, FR-21, FR-22)

Bảng commission_rule (FR-21, FR-22) — id (PK), category_id (FK logic → Catalog category.id), commission_percent (numeric(5,2), CHECK 0-100), hold_days (integer, nullable, CHECK 3-7 khi có giá trị — khuyến nghị theo brief mục 2/5; NULL = áp dụng mặc định toàn sàn 5 ngày theo BR-04), effective_from (date), effective_to (date, nullable), updated_by (FK logic, admin), updated_at. Index (category_id, effective_from DESC) — cho phép lịch sử thay đổi % hoa hồng và số ngày hold theo ngành hàng. Khi Commission & Payout Service tạo payout_hold (xem dưới), hold_until_date = OrderDelivered.deliveredAt + (commission_rule.hold_days nếu có giá trị, ngược lại mặc định 5 ngày toàn sàn).

Bảng commission_transaction (FR-20, FR-21) — id (PK), order_seller_id (FK logic → Cart & Order order_seller.id), seller_id (FK logic), gross_amount (numeric), commission_amount (numeric), net_amount (numeric), calculated_at. Index (seller_id, calculated_at) phục vụ dashboard doanh thu seller (FR-20).

Bảng payout (FR-20, FR-22) — id (PK), seller_id (FK logic), period_start (date), period_end (date), total_net_amount (numeric(14,2)) [Payment], bank_transfer_ref (varchar) [Payment], status (scheduled/processing/paid/failed), scheduled_at, paid_at. Index (seller_id, period_start DESC). UNIQUE(seller_id, period_start, period_end) tránh payout trùng chu kỳ.

Bảng payout_hold (FR-22, FR-25) — id (PK), commission_transaction_id (FK), hold_until_date (date — tính từ OrderDelivered + commission_rule.hold_days áp dụng, xem công thức ở bảng commission_rule phía trên), release_status (holding/released/disputed_frozen/reversed), released_at. Ý nghĩa các trạng thái:

  • holding: đang trong kỳ giữ tiền, chưa đến hold_until_date.
  • released: đã qua hold_until_date, không có Dispute mở, hoa hồng được đưa vào kỳ payout kế tiếp.
  • disputed_frozen: tạm giữ — có Dispute liên quan đang mở/chờ xử lý trước hold_until_date; có thể quay lại holding nếu Dispute bị từ chối (reject).
  • reversed: trạng thái kết thúc, vĩnh viễn — Dispute liên quan được duyệt hoàn tiền cho khách; hoa hồng bị loại khỏi payout hoàn toàn, không bao giờ chuyển sang released. Khác với disputed_frozen (tạm giữ chờ quyết định), reversed là kết quả cuối cùng sau khi đã có quyết định hoàn tiền.

Index (hold_until_date, release_status) phục vụ job quét hằng ngày để giải phóng tiền vào kỳ payout; job loại trừ mọi dòng có release_status = 'reversed' khỏi các lần quét tiếp theo (không xử lý lại).

5.2.7 Promotion & Loyalty Service (phục vụ FR-13, FR-14)

Bảng promotion (FR-13) — id (PK), code (UNIQUE), type (percent/fixed_amount), value (numeric), min_order_amount (numeric, nullable), valid_from, valid_to, usage_limit (int, nullable), created_by (FK logic, admin), status (active/expired/disabled).

Bảng promotion_usage (FR-13) — id (PK), promotion_id (FK), order_id (FK logic), customer_id (FK logic), discount_amount (numeric), used_at. UNIQUE(promotion_id, order_id).

Bảng loyalty_account (FR-14) — id (PK), customer_id (FK logic, UNIQUE — 1-1 với Customer), points_balance (int, CHECK >= 0), tier_id (FK → membership_tier), total_spend_12m (numeric — cửa sổ trượt 12 tháng theo giả định #5 mục 1.4), updated_at.

Bảng loyalty_transaction (FR-14) — id (PK), loyalty_account_id (FK), order_id (FK logic, nullable — null khi admin điều chỉnh thủ công), type (earn/redeem/expire/adjust), points (int, có thể âm), created_at. Index (loyalty_account_id, created_at DESC).

Bảng membership_tier (FR-14) — id (PK), name (Bạc/Vàng/Kim cương), min_spend_threshold (numeric), benefits_description. Dữ liệu cấu hình tĩnh, ít thay đổi. Ghi chú seed data: giá trị min_spend_threshold (VND) cho từng hạng hiện là placeholder tạm thời, chưa có con số cụ thể từ brief/mục 2 — cần chủ dự án xác nhận ngưỡng VND chính xác cho Bạc/Vàng/Kim cương trước khi seed dữ liệu production (xem assumptions, openQuestions).

5.2.8 Review Service (phục vụ FR-11)

Bảng review — id (PK), product_id (FK logic → Catalog product.id), customer_id (FK logic), order_item_id (FK logic → Cart & Order order_item.id, dùng để xác minh khách đã mua trước khi cho phép đánh giá), rating (int, CHECK 1-5), comment (text), status (visible/hidden_by_admin), created_at. UNIQUE(customer_id, order_item_id) — mỗi lượt mua chỉ đánh giá một lần. Index (product_id, status).

5.2.9 Notification Service (phục vụ FR-12)

Bảng notification_log — id (bigint, PK, identity), recipient_user_id (FK logic), channel (email/sms), template_code, related_entity_type (VD order, shipment), related_entity_id (uuid), status (queued/sent/failed), sent_at, error_message (nullable). Append-only, partition theo thời gian (xem 5.3.3). Index (recipient_user_id, sent_at DESC).

5.2.10 Shipping & Fulfillment Service (phục vụ FR-26)

Bảng shipment — id (PK), order_seller_id (FK logic → Cart & Order order_seller.id), carrier (GHN/GHTK), tracking_number, status (created/picked_up/in_transit/delivered/failed), estimated_delivery_date, created_at. Index (tracking_number), index (order_seller_id).

Bảng shipment_event — id (bigint, PK, identity), shipment_id (FK), event_status, event_time, raw_payload (jsonb — webhook gốc từ GHN/GHTK). Append-only, index (shipment_id, event_time).

5.2.11 Audit & Compliance Service (bổ sung v3 — theo findings bảo mật mục 8, phục vụ NFR-04, NFR-05)

Bảng audit_log (append-only) — id (bigint, PK, identity), actor_id (uuid, FK logic → Identity user_account.id), actor_role (varchar, snapshot vai trò tại thời điểm hành động — VD platform_admin/ops_staff/csr), action (varchar, VD kyc_document.verify, commission_rule.update, dispute.resolve, payout.retry, seller.lock, seller.unlock), resource_type (varchar, VD kyc_document/commission_rule/dispute/payout/seller), resource_id (uuid), before_json (jsonb, nullable — snapshot trạng thái trước khi thay đổi) [PII/Payment tuỳ ngữ cảnh], after_json (jsonb, nullable — snapshot trạng thái sau khi thay đổi) [PII/Payment tuỳ ngữ cảnh], ip_address (varchar/inet), user_agent (varchar), created_at (timestamptz, NOT NULL). Index (resource_type, resource_id, created_at DESC), index (actor_id, created_at DESC).

Vị trí đặt & cơ chế ghi: đặt tại một service audit riêng biệt (Audit & Compliance Service), sở hữu database riêng theo đúng nguyên tắc database-per-service ở 5.0 — không ghi trực tiếp vào một bảng dùng chung từ các service nghiệp vụ khác (tránh phá vỡ ranh giới đã chốt ở mục 3.1). Cơ chế: mỗi service nghiệp vụ khi thực hiện hành động nhạy cảm xuyên service (duyệt/từ chối KYC ở Seller Management, cập nhật commission_rule/hold_days ở Commission & Payout, quyết định Dispute ở Cart & Order, retry payout ở Commission & Payout, khoá/mở seller ở Seller Management) phát một domain event tương ứng (VD KycDocumentVerified, CommissionRuleUpdated, DisputeResolved, PayoutRetried, SellerLocked/SellerUnlocked) lên message broker (Kafka/MSK, theo mục 3.2); Audit & Compliance Service subscribe các event này và ghi append-only vào audit_log. Cách tiếp cận này tận dụng hạ tầng event-driven đã có sẵn thay vì mỗi service tự duy trì audit log riêng lẻ (khó tổng hợp khi CSR/Admin cần tra cứu xuyên service) — thiết kế API tra cứu (đọc audit_log, giới hạn scope admin/ops) thuộc phạm vi api-designer (mục 4).

Retention/partition: partition theo tháng (range trên created_at) do khối lượng ghi tăng theo mọi hành động nhạy cảm toàn sàn (tương tự notification_log/shipment_event — xem 5.3.3); retention tối thiểu 5 năm — đủ cho mục đích audit an ninh và bao trùm phần lớn hành động liên quan tài chính (commission/payout), dù ngắn hơn mốc 10 năm chứng từ kế toán riêng của payment/payout ở 5.3.6 (assumption, cần chủ dự án/pháp chế xác nhận mốc chính xác — xem openQuestions). Không áp dụng "quyền xoá" theo NĐ13/2023 cho bản ghi audit (ghi nhận hành động của actor vai trò vận hành/quản trị, không phải yêu cầu xoá dữ liệu cá nhân của Customer thông thường); có thể cân nhắc ẩn danh hoá ip_address/user_agent sau retention để giảm rủi ro PII thứ cấp.

5.3 Chiến lược dữ liệu

5.3.1 Cache (Redis — ElastiCache, theo mục 3.2)

Loại dữ liệu cache Vị trí TTL đề xuất Chiến lược invalidation
Catalog/Product detail (đọc nhiều, phục vụ NFR-01 <2s) Catalog & Inventory Service 5-15 phút Cache-aside; invalidate chủ động khi nhận event ProductUpdated/InventoryChanged thay vì chỉ chờ TTL hết hạn
Kết quả tìm kiếm/danh mục phổ biến (search subsystem) Search subsystem (OpenSearch + Redis) 1-5 phút cho query phổ biến, không cache query dài đuôi Invalidate theo event đồng bộ index; TTL ngắn vì tồn kho/giá thay đổi thường xuyên mùa flash sale
Session đăng nhập (JWT refresh/session state) Identity & Access Service Theo thời hạn session (VD 30 phút idle, 7 ngày remember-me) Xoá khi logout/đổi mật khẩu; TTL tự nhiên hết hạn
Giỏ hàng (Cart) của Customer đăng nhập Cart & Order Service 30 ngày (đồng bộ ghi xuống RDS định kỳ/khi checkout để không mất dữ liệu nếu Redis restart) Ghi-through (write-through) khi thêm/xoá item; TTL gia hạn mỗi lần cập nhật
Giỏ hàng Guest (theo session_id) Cart & Order Service 7 ngày Không cần đồng bộ RDS bền vững — chấp nhận mất nếu hết hạn (đúng ghi chú mục 3.2: "có thể chấp nhận mất dữ liệu tạm thời thấp")
Bảng tỷ giá quy đổi hiển thị (exchange_rate) Catalog & Inventory Service 1 giờ (chỉ hiển thị tham khảo theo FR-16, không dùng để thanh toán nên không cần realtime) Refresh theo batch job cập nhật tỷ giá hằng ngày/hằng giờ
Cấu hình hoa hồng đang hiệu lực (commission_rule, gồm cả hold_days) Commission & Payout Service 10 phút Invalidate khi Admin cập nhật (FR-21, bao gồm cập nhật hold_days qua PUT /v1/admin/commission-rules/{categoryId}) qua event CommissionRuleUpdated

5.3.2 Backup & Recovery

  • RDS PostgreSQL Multi-AZ (mọi service, theo mục 3.2): tự động failover đồng bộ trong AZ cùng vùng → RPO gần 0 cho lỗi hạ tầng tầng instance.
  • Automated backup + Point-in-Time Recovery (PITR): bật cho toàn bộ database-per-service; retention đề xuất 35 ngày cho các service giao dịch cốt lõi có dữ liệu tài chính/PII (Payment, Commission & Payout, Seller Management, Identity, Cart & Order); 14 ngày cho các service ít quan trọng hơn (Review, Notification, Promotion & Loyalty) — phù hợp NFR-08 (ưu tiên vận hành khác nhau theo mức độ nghiêm trọng).
  • Snapshot thủ công định kỳ + sao chép cross-region (DR): snapshot hằng ngày, lưu tối thiểu 90 ngày cho Payment/Commission & Payout/Seller Management (dữ liệu tài chính, đối soát) để phục vụ kiểm toán; sao chép sang region phụ (VD ap-southeast-1 ↔ region dự phòng) tối thiểu cho các service giao dịch cốt lõi nhằm đáp ứng NFR-03 (uptime 99.9%).
  • RTO/RPO gợi ý theo mức độ ưu tiên (đối chiếu NFR-08 — ưu tiên multi-AZ cho service giao dịch cốt lõi):
Nhóm service RPO gợi ý RTO gợi ý
Payment, Cart & Order, Identity & Access (giao dịch cốt lõi) ≤ 15 phút ≤ 1 giờ
Commission & Payout, Seller Management (tài chính, không realtime nhưng nhạy cảm) ≤ 1 giờ ≤ 4 giờ
Catalog & Inventory, Promotion & Loyalty, Shipping & Fulfillment ≤ 1 giờ ≤ 8 giờ
Review, Notification, Audit & Compliance (không ảnh hưởng giao dịch trực tiếp) ≤ 24 giờ ≤ 24 giờ
  • S3 (ảnh sản phẩm, KYC docs): bật versioning + cross-region replication cho bucket KYC (dữ liệu PII pháp lý, cần bảo toàn lâu dài); lifecycle policy chuyển ảnh sản phẩm ít truy cập sang storage class rẻ hơn (Infrequent Access) sau 90 ngày.

5.3.3 Partitioning

Do scale: large (hàng trăm nghìn SKU, hàng trăm nghìn–hàng triệu user, giao dịch tích luỹ liên tục), áp dụng partitioning theo thời gian (range partitioning theo created_at/tháng hoặc quý) cho các bảng có tốc độ ghi cao và tăng trưởng không giới hạn:

Bảng Kiểu partition Lý do
order, order_seller, order_item, order_status_history Range theo tháng Khối lượng đơn hàng tích luỹ lớn nhất hệ thống; tách partition giúp truy vấn "đơn hàng gần đây" nhanh và archive/xoá đơn cũ dễ dàng
payment, payment_reconciliation_log Range theo tháng Cùng nhịp tăng trưởng với order; phục vụ đối soát theo kỳ
commission_transaction, payout_hold Range theo tháng Gắn với chu kỳ payout hàng tuần; truy vấn chủ yếu theo kỳ gần nhất
loyalty_transaction Range theo quý Tăng trưởng theo số đơn hàng, truy vấn chủ yếu lịch sử 12 tháng gần nhất (theo tier)
notification_log, shipment_event Range theo tháng Log append-only khối lượng lớn nhất, giá trị truy vấn giảm nhanh theo thời gian → dễ archive/drop partition cũ
audit_log (v3) Range theo tháng Ghi từ mọi hành động nhạy cảm toàn sàn qua event (KYC, commission/hold_days, dispute, payout retry, khoá/mở seller); retention dài hạn (5 năm, xem 5.2.11/5.3.6) nên cần partition để archive theo mốc kiểm toán mà không ảnh hưởng hiệu năng ghi/đọc gần đây

Không áp dụng partitioning cho các bảng còn lại (product, product_variant, category, user_account, seller, review, promotion...) — khối lượng bậc hàng trăm nghìn đến vài triệu dòng vẫn nằm trong khả năng xử lý tốt của một bảng B-tree index thông thường trên RDS instance lớn; việc partition thêm sẽ tăng độ phức tạp vận hành không cần thiết ở MVP.

5.3.4 Sharding

Chưa áp dụng sharding ở MVP. Lý do: kiến trúc database-per-service (mục 3.1) đã cho phép scale-out theo domain (VD Catalog & Search có thể scale độc lập khỏi Cart & Order khi tải đỉnh flash sale) — đây là lớp scale đầu tiên và đã đủ đáp ứng NFR-02 với quy mô "large" hiện tại (hàng trăm nghìn SKU, hàng chục nghìn concurrent peak). Sharding trong nội bộ một service (VD sharding order theo customer_id/seller_id) chỉ nên cân nhắc khi:

  • Một service đơn lẻ vượt quá khả năng của RDS instance lớn nhất khả dụng (write IOPS/storage), hoặc
  • Có số liệu thực tế cho thấy tăng trưởng vượt giả định hiện tại (VD hàng chục triệu đơn hàng/năm).

Đây là quyết định hoãn có căn cứ, không phải bỏ sót — cần đánh giá lại khi có số liệu tải thực tế sau go-live (ghi ở openQuestions).

5.3.5 Migration dữ liệu cũ

Không áp dụng — dự án greenfield, theo brief mục 3/5: "không có hệ thống cũ cần tích hợp/migrate". Dữ liệu khởi tạo (seed) chỉ gồm dữ liệu cấu hình tĩnh: language, currency, membership_tier (giá trị min_spend_threshold tạm thời, chờ xác nhận — xem 5.2.7), category gốc, commission_rule mặc định theo ngành hàng ban đầu (bao gồm hold_days — mặc định để NULL cho hầu hết ngành hàng, dùng giá trị toàn sàn 5 ngày, trừ khi có ngành hàng đặc thù cần cấu hình riêng ngay từ đầu).

5.3.6 Retention & xoá dữ liệu (liên quan NĐ13/2023 — bảo vệ dữ liệu cá nhân)

Loại dữ liệu Đề xuất retention Ghi chú
Tài khoản Customer đã đóng/xoá theo yêu cầu (quyền xoá dữ liệu cá nhân — NĐ13/2023) Ẩn danh hoá (anonymize) email, phone, full_name, địa chỉ trong vòng 30 ngày kể từ yêu cầu hợp lệ, giữ lại order/payment liên quan ở dạng tách rời định danh (cần cho đối soát/kế toán) Cần quy trình xoá/ẩn danh cụ thể — chi tiết kỹ thuật (mã hoá, key rotation) thuộc mục 8
KYC documents (giấy phép kinh doanh, CMND) Tối thiểu 5 năm sau khi seller ngừng hoạt động (giả định theo thông lệ lưu trữ hồ sơ pháp lý — brief chưa quy định số năm cụ thể) assumption — cần xác nhận với chủ dự án/pháp chế
Payment, commission_transaction, payout (dữ liệu tài chính) Tối thiểu 10 năm (thông lệ lưu trữ chứng từ kế toán tại Việt Nam) assumption — cần xác nhận yêu cầu kế toán/thuế cụ thể
audit_log (audit trail hành động nhạy cảm xuyên service — v3) Tối thiểu 5 năm assumption — cần chủ dự án/pháp chế xác nhận mốc chính xác cho audit an ninh/tuân thủ; xem 5.2.11
notification_log, shipment_event (log vận hành) 90 ngày, sau đó archive lạnh hoặc xoá Không có giá trị pháp lý bắt buộc lưu lâu dài
review, wishlist_item Không giới hạn trong khi tài khoản còn hoạt động; xoá khi Customer yêu cầu xoá tài khoản

5.4 Ma trận truy vết dữ liệu → yêu cầu chức năng

FR Mô tả ngắn Entity/bảng chính
FR-01 Đăng ký & đăng nhập Customer user_account
FR-02 Đăng nhập mạng xã hội oauth_identity
FR-03 Hồ sơ & địa chỉ giao hàng customer_profile, customer_address
FR-04 Danh mục & tìm kiếm đa seller category, product, product_variant (+ chỉ mục OpenSearch phái sinh)
FR-05 Giỏ hàng đa seller cart, cart_item
FR-06 Checkout & tách đơn theo seller order, order_seller, order_item
FR-07 Thanh toán payment, payment_reconciliation_log
FR-08 Quản lý đơn hàng (khách hàng) order, order_seller, order_status_history
FR-09 Đổi trả & khiếu nại return_request, dispute
FR-10 Wishlist wishlist_item
FR-11 Đánh giá sản phẩm review
FR-12 Thông báo đơn hàng notification_log
FR-13 Khuyến mãi & mã giảm giá promotion, promotion_usage
FR-14 Loyalty/điểm thưởng loyalty_account, loyalty_transaction, membership_tier
FR-15 Đa ngôn ngữ giao diện language, product_i18n, category_i18n
FR-16 Đa tiền tệ hiển thị currency, exchange_rate
FR-17 Đăng ký & KYC seller seller, kyc_document
FR-18 Quản lý sản phẩm & tồn kho (seller) product, product_variant, inventory_stock
FR-19 Quản lý đơn hàng (seller) order_seller, order_item
FR-20 Dashboard doanh thu/payout (seller) commission_transaction, payout
FR-21 Cấu hình hoa hồng theo ngành hàng commission_rule (gồm hold_days theo ngành hàng)
FR-22 Payout định kỳ cho seller payout, payout_hold, seller_bank_account
FR-23 Quản trị seller seller (cột status)
FR-24 Quản trị catalog toàn sàn product (cột status)
FR-25 Xử lý tranh chấp & khiếu nại dispute, payout_hold (trạng thái disputed_frozen/reversed)
FR-26 Xử lý tồn kho & vận chuyển inventory_stock, shipment, shipment_event
FR-27 Xác thực đa yếu tố (MFA) user_account (cột mfa_enabled, và v3: failed_login_count/locked_until/last_failed_login_at hỗ trợ khoá tài khoản sau nhiều lần đăng nhập sai), mfa_device

(v3) Bảng audit_log (Audit & Compliance Service, 5.2.11) là dữ liệu cross-cutting, không gắn với một FR nghiệp vụ cụ thể — phục vụ NFR-04 (bảo mật, audit trail) và NFR-05 (tuân thủ pháp lý) cho các hành động nhạy cảm xuyên service: duyệt/từ chối KYC (liên quan FR-17), cấu hình commission/hold_days (FR-21), quyết định dispute (FR-09/FR-25), retry payout (FR-22), khoá/mở seller (FR-23).

5.5 Ghi chú cho security-architect (rà soát mã hoá tại mục 8)

Danh sách cột đã đánh dấu [PII]/[Payment] cần ưu tiên rà soát mã hoá at-rest (KMS), kiểm soát truy cập theo vai trò, và masking khi hiển thị:

  • PII: user_account.email/phone, mfa_device.secret_encrypted, customer_profile.full_name/date_of_birth, customer_address.recipient_name/phone/address_line, seller.tax_code/business_license_number, kyc_document.file_url_s3 (trỏ tới object S3 chứa ảnh giấy tờ — bản thân object cũng cần mã hoá S3-side), seller_bank_account.account_holder_name.
  • Payment: payment.amount/gateway_transaction_ref, seller_bank_account.account_number, payout.total_net_amount/bank_transfer_ref.
  • (v3) user_account.failed_login_count/locked_until/last_failed_login_at — không phải PII/Payment nhưng là dữ liệu bảo mật nhạy cảm (chống brute-force); cần kiểm soát truy cập ghi chỉ qua luồng xác thực nội bộ, không expose trực tiếp qua API đọc công khai.
  • (v3) audit_log.before_json/after_json — nội dung thay đổi tuỳ resource_type (VD snapshot kyc_document, seller_bank_account, commission_rule có thể chứa PII/Payment như account_number, tax_code): đề xuất security-architect quy định rõ (a) mã hoá at-rest cho toàn bảng audit_log tối thiểu bằng KMS, (b) cân nhắc redact/loại trừ các trường cực nhạy cảm (VD số tài khoản ngân hàng đầy đủ) khỏi snapshot trước khi ghi, chỉ giữ giá trị đã che (mask) hoặc hash để phục vụ audit mà không nhân bản rủi ro rò rỉ dữ liệu.
  • Đề xuất: mã hoá cột ở tầng ứng dụng (application-level encryption) cho account_number, secret_encrypted, tax_code, business_license_number; các cột PII còn lại tối thiểu dựa vào mã hoá at-rest của RDS (KMS) + TLS in-transit + IAM/role-based access theo service.

5.6 Findings & vấn đề cần làm rõ thêm

  • Glossary mục 1.3 không liệt kê rõ bảng nào lưu "Language"/"Currency" là entity độc lập hay chỉ là thuộc tính cấu hình — đã quyết định tạo bảng cấu hình riêng (language, currency, exchange_rate) đặt tại Catalog & Inventory Service theo ghi chú cross-cutting ở mục 3.1; cần xác nhận lại nếu kiến trúc sư muốn tách thành "Platform Config Service" riêng khi có thêm nhu cầu cấu hình khác.
  • NFR về retention dữ liệu (thời gian lưu KYC, dữ liệu tài chính, log) chưa được brief hoặc mục 2 quy định cụ thể — mục 5.3.6 đưa ra giả định thận trọng theo thông lệ, cần chủ dự án/pháp chế xác nhận lại con số chính xác trước khi go-live (đặc biệt retention KYC liên quan NĐ13/2023 và luật kế toán, và nay thêm retention audit_log — xem 5.2.11).
  • Số liệu khối lượng/tăng trưởng cụ thể theo thời gian (VD số đơn hàng/tháng dự kiến năm 1, năm 2) không có trong brief — quyết định partitioning ở 5.3.3 và ngưỡng cân nhắc sharding ở 5.3.4 dựa trên giả định định tính "large" ở mức bậc; cần rà soát lại khi có số liệu thực tế/kết quả load test.
  • (v2 — theo review mục 6) Đã bổ sung commission_rule.hold_days (nullable, fallback mặc định toàn sàn 5 ngày theo BR-04) để hiện thực hoá cấu hình hold theo ngành hàng; đã bổ sung trạng thái kết thúc reversed vào payout_hold.release_status để phân biệt loại hoa hồng vĩnh viễn (khi Dispute được duyệt hoàn tiền) với disputed_frozen (tạm giữ) — xem 5.2.6. membership_tier.min_spend_threshold vẫn là placeholder chờ chủ dự án xác nhận ngưỡng VND cụ thể — xem 5.2.7.
  • (v3 — theo review findings bảo mật mục 8) Đã bổ sung 3 cột chống brute-force vào user_account (failed_login_count, locked_until, last_failed_login_at — 5.2.1); ngưỡng số lần sai/khoảng thời gian khoá cụ thể để security-architect quy định ở mục 8. Đã bổ sung service mới Audit & Compliance Service với bảng audit_log append-only (5.2.11), cập nhật ERD (5.1.1 ghi chú, 5.1.2 thêm bounded context mới), partitioning (5.3.3), retention (5.3.6), ma trận truy vết (5.4) và ghi chú bảo mật cho before_json/after_json (5.5). Cơ chế đặt tại service riêng nhận qua event stream (Kafka/MSK) là quyết định thiết kế của mục 5 — cần kiến trúc sư (mục 3) xác nhận bổ sung service này vào sơ đồ kiến trúc tổng thể nếu chưa có, và api-designer (mục 4) bổ sung endpoint đọc audit log có kiểm soát scope admin/ops nếu cần.