# DOM — Domain Model — | | | |---|---| | **Version** | 1.0 | | **Date** | YYYY-MM-DD | | **Author** | (skill sa-2-architecture) | | **Status** | 🟡 Draft | | **Approved by** | Tech Lead: — · BE Lead: — | | **Source** | SAD_… v1.0 · DAT_… v1.0 · BR_… của BA · BACKLOG_… của BA | | **Scope** | | | **Confidence** | 🟡 | ## Change Log | Version | Date | Người sửa | Thay đổi | ADR | |---|---|---|---|---| | 1.0 | | | Bản đầu | — | > **Thẩm quyền khi mâu thuẫn:** sơ đồ thắng về **quan hệ và luồng**; bảng thắng về **ràng buộc và > con số**. `BR` của BA là **nguồn** của mọi invariant ở đây; `DOM` chỉ nói invariant đó sống ở > class nào. Lệch với `BR` ⇒ `OQ`, không tự sửa rule. > **Vì sao có tài liệu này:** `DAT` nói *ai sở hữu dữ liệu gì*, `PDM` nói *bảng vật lý trông thế > nào*. Không có `DOM`, dev tự suy ra aggregate từ bảng — và mỗi người suy một kiểu, transaction > boundary lệch nhau, invariant bị kiểm ở ba chỗ hoặc không chỗ nào. --- ## 1. Bounded context và aggregate *Một dòng mỗi aggregate. Aggregate là **ranh giới transaction**: mọi invariant trong aggregate được kiểm trong một lần ghi; giữa các aggregate chỉ có eventual consistency (`DAT` §4).* | Bounded context | `CMP-nn` sở hữu | Aggregate root | Entity con | Value object | Invariant chính (`BR`) | Sự kiện phát ra | |---|---|---|---|---|---|---| | Cart & Order | `CMP-04` | `Order` | `OrderSeller`, `OrderItem` | `Money`, `Address` | `BR-014` tổng tiền = Σ dòng | `OrderPlaced` | | | | | | | | | 🔴 **Một entity thuộc đúng một aggregate.** Entity xuất hiện ở hai aggregate ⇒ một trong hai chỉ được giữ **id tham chiếu** (không giữ object), ghi rõ ở §3. --- ## 2. Class diagram theo bounded context *Một sơ đồ mỗi bounded context, ≤ 12 class. Chỉ vẽ thuộc tính nghiệp vụ và phương thức đổi trạng thái; getter/setter, DTO, repository không vẽ ở đây (thuộc `AGD`).* ### 2.1 ```mermaid classDiagram class Order { +OrderId id +CustomerId customerId +Money totalAmount +OrderStatus status +place(cart) Order +cancel(reason) } class OrderSeller { +SellerId sellerId +Money subtotal +confirm() } class OrderItem { +ProductVariantId variantId +int quantity +Money unitPriceSnapshot } class Money { <> +decimal amount +Currency currency } Order "1" *-- "1..*" OrderSeller : splits into OrderSeller "1" *-- "1..*" OrderItem : contains Order ..> Money OrderItem ..> Money ``` **Bảng đi kèm** *(W13 — sơ đồ không nói được invariant kiểm ở đâu và ai được gọi)*: | Class | Loại | Thuộc tính nghiệp vụ | Invariant (`BR`) | Kiểm ở phương thức | Ai được gọi (`ROLE`/`CMP`) | Bảng `PDM` | |---|---|---|---|---|---|---| | `Order` | aggregate root | `totalAmount`, `status` | `BR-014`, `BR-021` | `place()`, `cancel()` | `ROLE-01` qua `IF-002` | `TBL-012 order` | | `Money` | value object | `amount`, `currency` | `BR-003` không âm | constructor | — | cột `*_amount` | **Quan hệ:** `*--` composition (cùng transaction) · `o--` aggregation (khác vòng đời) · `..>` phụ thuộc · `-->` tham chiếu bằng id sang aggregate khác. ### 2.2 *(cùng cấu trúc)* --- ## 3. Tham chiếu xuyên aggregate *Chỗ hay sai nhất: giữ object của aggregate khác thay vì id, rồi load cả cây.* | Từ aggregate | Tham chiếu tới | Bằng gì | Đọc dữ liệu kia bằng | Trễ chấp nhận (`DAT` §4) | |---|---|---|---|---| | `Order` | `Customer` | `CustomerId` | `IF-0nn` / read model | eventual ≤ … | | `OrderItem` | `ProductVariant` | `ProductVariantId` + snapshot giá/tên | snapshot lúc đặt | không cần đồng bộ | 🔴 **Snapshot hay tham chiếu sống?** Giá, tên, địa chỉ lúc giao dịch phải là **snapshot** (thay đổi sau đó không được đổi lịch sử). Ghi rõ từng trường, đây là nguồn của nhiều bug đối soát. --- ## 4. Vòng đời trạng thái → class *Bảng chuyển trạng thái nằm ở `BR` §3; đây chỉ map xuống class và phương thức.* | Entity | Enum trạng thái | Thuộc `BR` | Phương thức chuyển | Guard kiểm ở đâu | Ghi vết (`SEC` §audit) | |---|---|---|---|---|---| | `Order` | `OrderStatus` | `BR-021` | `confirm()`, `cancel()` | trong aggregate | `order_status_history` | --- ## 5. Domain event | Sự kiện | Phát từ aggregate | Khi nào | Payload tối thiểu | Người nhận (`CMP`) | Contract (`CTR`) | |---|---|---|---|---|---| | `OrderPlaced` | `Order` | `place()` commit xong | `orderId`, `customerId`, `sellerIds[]`, `totalAmount` | `CMP-06`, `CMP-09` | `contracts/asyncapi/order-events.yaml#OrderPlaced` | 🔴 Payload event **không chứa PII** ngoài id trừ khi `SEC` §5 cho phép đích danh. --- ## 6. Đối chiếu | Kiểm | Kết quả | Hành động | |---|---|---| | Mọi thực thể trong `DAT` §1 có class ở §2 | n/n | | | Mọi `BR` loại "ràng buộc dữ liệu"/"tính toán" có class kiểm | n/n | | | Mọi entity có trạng thái ở `BR` §3 có enum + phương thức ở §4 | n/n | | | Mọi class root có bảng trong `PDM` §2 | n/n | *(điền khi `PDM` xong)* | ## 7. Giả định & Ngoài phạm vi **Giả định:** | ID | Giả định | Cách xác minh | Nếu sai | |---|---|---|---| **Ngoài phạm vi:** - *(ví dụ: không mô hình hoá module báo cáo — chỉ đọc, dùng read model của `DAT` §4)* ## 8. Open Questions | ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì | Hệ quả nếu trả lời ngược | |---|---|---|---|---|---|