49 KiB
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_idtrong Cart & Order Service trỏ tớiseller.idcủ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ặcgen_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ùngBIGINT 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_casesố ít (VDproduct,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-architectrà 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,CSRkhông xuất hiện là entity dữ liệu riêng vì chỉ là vai trò (role) trong bảnguser_accountcủa Identity Service (5.2.1);Language,Currencylà bảng cấu hình dùng chung, đặt tại 5.2.2. Bảngaudit_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 quaresource_type/resource_idchứ 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_LOGkhô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 quaresource_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 địnhuuid 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 | ||
| 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/ProductCreatedtừ 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 đếnhold_until_date.released: đã quahold_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ướchold_until_date; có thể quay lạiholdingnế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 sangreleased. Khác vớidisputed_frozen(tạm giữ chờ quyết định),reversedlà 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 snapshotkyc_document,seller_bank_account,commission_rulecó thể chứa PII/Payment nhưaccount_number,tax_code): đề xuấtsecurity-architectquy định rõ (a) mã hoá at-rest cho toàn bảngaudit_logtố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úcreversedvàopayout_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ớidisputed_frozen(tạm giữ) — xem 5.2.6.membership_tier.min_spend_thresholdvẫ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-architectquy định ở mục 8. Đã bổ sung service mới Audit & Compliance Service với bảngaudit_logappend-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 chobefore_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.