Files
2026-09-22 13:46:36 +07:00

158 lines
5.9 KiB
Markdown

# DOM — Domain Model — <PROJECT>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <SA> (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 <Bounded context>
<!-- archify: mermaid-only -->
```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 {
<<value object>>
+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 <Bounded context tiếp theo>
*(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 |
|---|---|---|---|---|---|