Xem lại phần 5: DDD [Part 5]: Transaction Script & Active Record – 2 patterns để giải quyết business logic đơn giản
📜 Lịch sử & nguồn gốc
- Martin Fowler là người đầu tiên định nghĩa
Domain Model Pattern
trong cuốn Patterns of Enterprise Application Architecture. - Fowler trích dẫn rằng Eric Evans đang viết một cuốn sách về Domain Model – đó chính là tác phẩm nổi tiếng: Domain-Driven Design – Tackling Complexity in the Heart of Software.
🔹 Cuốn sách của Evans không chỉ nhấn mạnh khái niệm “Domain Model” mà còn giới thiệu tập hợp các mẫu thiết kế cụ thể (gọi là Tactical DDD):
Value Object
Entity
Aggregate
Repository
Domain Service
Khi nào nên dùng Domain Model?
Khác với các mô hình đơn giản như transaction script hay active record, Domain Model được thiết kế để xử lý logic nghiệp vụ phức tạp, nơi mà:
- ❌ Không còn đơn thuần là CRUD.
- ✅ Thay vào đó là chuyển trạng thái phức tạp, ràng buộc logic, và invariants – các quy tắc bắt buộc phải duy trì đúng trong mọi tình huống.
Ví dụ: Một hệ thống Help Desk với các yêu cầu như:
- 📩 Người dùng và support agent trao đổi qua ticket.
- ⚠ Mỗi ticket có mức ưu tiên (low → urgent).
- ⏰ Agent phải phản hồi theo SLA.
- 🆘 Không phản hồi đúng hạn → khách hàng có thể escalate.
- 📉 Escalation rút ngắn 33% thời gian phản hồi.
- 🔁 Ticket escalated mà không được mở lại trong 50% thời gian còn lại → tự động chuyển agent khác.
- ⏳ Tự động đóng ticket nếu khách không trả lời sau 7 ngày (trừ escalated).
- 🔓 Ticket chỉ được reopen trong vòng 7 ngày.
Đây là những luồng nghiệp vụ đan xen phức tạp, không thể xử lý tốt bằng Active Record vì dễ vi phạm các ràng buộc hoặc trùng logic.
Định nghĩa Domain Model
“A domain model is an object model of the domain that incorporates both behavior and data.”
Domain Model là tập hợp các Plain Old Objects chứa cả:
- 🎯 Dữ liệu.
- 🔄 Logic nghiệp vụ.
Các pattern chiến thuật (tactical patterns) trong DDD chính là “vũ khí” để hiện thực Domain Model:
- Value Object.
- Entity.
- Aggregate.
- Domain Event.
- Domain Service.
Tất cả đều tập trung vào một điều cốt lõi: Business Logic First.
Giảm độ phức tạp – theo nguyên lý của Eliyahu Goldratt
Complexity = số degree of freedom cần thiết để mô tả trạng thái hệ thống.
So sánh:
- ClassA: có 5 biến → 5 mức tự do
- ClassB: nhiều logic nội tại, nhưng chỉ cần A và D để suy ra phần còn lại → chỉ 2 mức tự do
👉 Từ đó, Aggregate
và Value Object
giúp giảm complexity bằng cách:
- Đóng gói các ràng buộc
- Không cho phép thay đổi tùy ý từ bên ngoài
- Tăng khả năng kiểm soát và dự đoán hành vi hệ thống
1. Value Object
Đặc điểm:
- 🧩 Nhận diện bởi giá trị, không cần ID.
- 🔄 Bất biến (immutable).
- ✅ Có logic nghiệp vụ xử lý bên trong.
🔶 Ví dụ: Color
class Color { int _red, _green, _blue; }
Color(255,0,0)
≠Color(255,0,1)
.- Không cần
ColorId
.
📛 Nếu thêm ColorId
sẽ tạo lỗ hổng logic – có thể có 2 màu giống hệt nhưng ID khác → lỗi so sánh.
❗ Primitive Obsession: code smell cần tránh
Ví dụ Person
chứa toàn string
cho email, phone, v.v. khiến:
- Validation bị lặp.
- Khó kiểm soát logic đúng lúc.
- Gây lỗi khi maintain.
🔄 Sửa bằng cách dùng Value Object:
class Person { private PersonId _id; private Name _name; private PhoneNumber _mobile; private EmailAddress _email; ... }
Lợi ích:
- 🎯 Rõ nghĩa (giảm ambiguity).
- 🧪 Validation và logic nằm bên trong Value Object.
- 🧠 Mô hình hóa đúng domain → code dễ đọc, dễ test.
📐 Value Object giúp:
- 🚀 Convert giữa đơn vị đo.
- 📏 So sánh chính xác.
- ☎️ Xử lý logic riêng của số điện thoại (nước, loại).
- 🎨 Mix màu để ra màu mới.
🔁 Mỗi thay đổi → tạo object mới → tránh mutation → thread-safe.
📛 Khi nào dùng Value Object?
- Khi đại diện cho thuộc tính của entity.
- Khi giá trị quyết định bản chất (màu sắc, độ cao, email…).
- Không cần ID riêng.
- Đặc biệt quan trọng với money, status, password, v.v.
2. Entity
Khác với Value Object, Entity:
- Cần ID riêng biệt.
- Có thể thay đổi (mutable).
- Dùng khi danh tính quan trọng hơn giá trị.
class Person { public readonly PersonId Id; public Name Name; }
👉 Mỗi Person
cần ID vì có thể có 2 người tên giống nhau nhưng khác danh tính.

3. Aggregate
Là một hoặc nhiều Entity được gom lại thành một khối:
- 📌 Có ràng buộc logic mạnh giữa các phần.
- 🔐 Chỉ aggregate root được phép thay đổi dữ liệu.
- 🧱 Là ranh giới consistency và transaction.
🔁 Mỗi thao tác trên Aggregate là một command:
ticket.Execute(new Escalate(reason));
❗ Quan trọng:
- 🧯 Bảo vệ consistency bằng version check (optimistic concurrency).
- ✅ Chỉ 1 Aggregate được commit trong 1 transaction.
🏗 Thiết kế Aggregate đúng cách
- Mọi entity/value object cần strong consistency → nằm trong aggregate.
- Những gì chỉ cần eventual consistency → nằm ngoài (chỉ tham chiếu qua ID).
class Ticket { List<Message> _messages; // ✅ thuộc aggregate UserId _customer; // ❌ chỉ reference }
📌 Rule: Nếu một phần dữ liệu mà sai sẽ gây lỗi nghiệp vụ → nó phải nằm trong aggregate.
🌳 Aggregate Root
Chỉ 1 entity duy nhất là public interface để tương tác với aggregate.
ticket.Execute(new AcknowledgeMessage(msgId));
✅ Không thể sửa trực tiếp message
– phải thông qua ticket
.

4. Domain Events
Miêu tả điều đã xảy ra trong domain:
- Tên phải ở thì quá khứ.
- Ví dụ:
{ "event-type": "ticket-escalated", "ticket-id": "...", "reason": "missed-sla" }

🟡 Dùng để:
- Gửi thông báo ra ngoài.
- Tách biệt các hành vi phụ.
- Kích hoạt các nghiệp vụ khác.
5. Domain services
Dùng khi:
- Logic không thuộc riêng aggregate nào.
- Cần đọc dữ liệu từ nhiều aggregate.
- Không chứa trạng thái (stateless).
Ví dụ:
- Tính deadline phản hồi dựa trên: ticket → policy → lịch làm việc.
class ResponseTimeFrameCalculationService { ResponseTimeframe Calculate(...) { ... } }
❗ Không phải microservice hay service layer. Đây là 1 lớp chứa logic nghiệp vụ.
Managing Complexity
Như đã đề cập ở phần mở đầu chương, các mẫu thiết kế aggregate và value object được giới thiệu như một công cụ để xử lý độ phức tạp trong việc triển khai logic nghiệp vụ. Vậy tại sao hai mẫu thiết kế này lại giúp quản lý độ phức tạp?
Trong cuốn sách The Choice, chuyên gia quản lý Eliyahu M. Goldratt đưa ra một định nghĩa rất rõ ràng và mạnh mẽ về tính phức tạp của hệ thống. Theo Goldratt, khi nói về độ phức tạp của một hệ thống, ta quan tâm đến hai khía cạnh:
- Mức độ khó để kiểm soát hệ thống
- Mức độ khó để dự đoán hành vi của hệ thống
Hai yếu tố này phụ thuộc vào khái niệm gọi là “degrees of freedom” (mức độ tự do) – tức là số lượng điểm dữ liệu cần thiết để mô tả trạng thái của hệ thống.
🧪 Ví dụ minh họa:
public class ClassA { public int A { get; set; } public int B { get; set; } public int C { get; set; } public int D { get; set; } public int E { get; set; } } public class ClassB { private int _a, _d; public int A { get => _a; set { _a = value; B = value / 2; C = value / 3; } } public int B { get; private set; } public int C { get; private set; } public int D { get => _d; set { _d = value; E = value * 2; } } public int E { get; private set; } }
➡️ Phân tích:
- ClassA có 5 biến – tức là cần 5 giá trị để mô tả trạng thái ⇒ 5 degrees of freedom.
- ClassB tuy có 5 biến, nhưng chỉ cần biết
A
vàD
, các biến còn lại được tính toán ⇒ chỉ có 2 degrees of freedom.
=> ✅ ClassB ít phức tạp hơn ClassA, vì nó dễ kiểm soát và dự đoán hơn. Chính vì vậy, việc đóng gói các ràng buộc (invariants) vào trong class giúp giảm độ phức tạp.
🧱 Value Objects và Aggregates – Giảm Complexity
- Toàn bộ logic nghiệp vụ liên quan đến một value object nằm trọn trong chính nó.
- Aggregate chỉ có thể được thay đổi bởi chính các method của nó.
- Cả hai giúp bảo vệ các ràng buộc nghiệp vụ (invariants) → từ đó giảm degrees of freedom.
💡 Vì Domain Model Pattern chỉ nên áp dụng cho các miền nghiệp vụ phức tạp, có thể xem rằng chúng luôn là các core subdomain – trung tâm của phần mềm.
✅ Kết luận
Domain Model Pattern được thiết kế để xử lý các trường hợp có logic nghiệp vụ phức tạp. Nó gồm 3 khối xây dựng chính:
- Value Objects
- Mô hình hóa các khái niệm được xác định bằng giá trị, không cần ID.
- Immutable – thay đổi bất kỳ trường nào đều tạo ra object mới.
- Chứa cả data và hành vi, giúp code rõ ràng và dễ test.
- Aggregates
- Một hierarchy gồm các entity và value object, chia sẻ giới hạn giao dịch (transactional boundary).
- Chỉ có thể thay đổi thông qua public interface.
- Đóng vai trò là điểm bảo vệ consistency và atomicity.
- Có thể phát hành domain events để thông báo thay đổi quan trọng.
- Domain Services
- Đối tượng stateless để chứa logic nghiệp vụ không thuộc aggregate hay value object cụ thể nào.
- Hữu ích cho các thao tác tính toán hoặc phối hợp thông tin giữa nhiều aggregate.
Tất cả những building blocks này giúp chúng ta đóng gói và cô lập logic nghiệp vụ, từ đó tránh duplication và giảm complexity ở tầng ứng dụng.
Để lại một bình luận
Bạn phải đăng nhập để gửi bình luận.