Files
sys-analysis-design/docs/sections/05-thiet-ke-du-lieu.md
Leonard-ThindPad-P50 c81f249920 init git
2026-09-08 10:26:21 +07:00

697 lines
49 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
section: "05"
title: Thiết kế dữ liệu
status: approved
version: 3
reviewer_notes: ""
---
# 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)
```mermaid
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**
```mermaid
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**
```mermaid
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**
```mermaid
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**
```mermaid
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**
```mermaid
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**
```mermaid
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**
```mermaid
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**
```mermaid
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)**
```mermaid
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.