--- 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.