DDD: Phần 6 – Những Domain Model Patterns giải quyết business logic phức tạp

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:

  1. Value Object.
  2. Entity.
  3. Aggregate.
  4. Domain Event.
  5. 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ừ đó, AggregateValue 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

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

🔁 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ấtpublic 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ế aggregatevalue 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:

  1. Mức độ khó để kiểm soát hệ thống
  2. 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 AD, 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:

  1. 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.
  2. 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.
  3. 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.

Một bình luận cho “DDD: Phần 6 – Những Domain Model Patterns giải quyết business logic phức tạp”

  1. […] Xem lại phần 6: DDD [Part 6]: Những Domain Model Patterns giải quyết business logic phức tạp […]

Để lại một bình luận