ThanhNguyen logo
thanh_nguyen
domain-driven-design

Domain Driven Design là gì? Khái niệm & Các thành phần cốt lõi

Domain Driven Design là gì? Khái niệm & Các thành phần cốt lõi
7 min read
#domain-driven-design

🌐 1. Domain

👉 Khái niệm:

"Domain" là phạm vi nghiệp vụ hoặc lĩnh vực chuyên môn mà hệ thống phần mềm của bạn đang giải quyết. Đây là cái mà doanh nghiệp quan tâm nhất, là lý do tồn tại của hệ thống. Việc hiểu rõ domain giúp đội ngũ phát triển và chuyên gia nghiệp vụ cùng nói chung một ngôn ngữ, tránh hiểu nhầm khi xây dựng phần mềm.

🛒 Ví dụ thực tế: Hệ thống thương mại điện tử

Các domain trong một hệ thống thương mại điện tử gồm:

  • Đặt hàng (Ordering): Quản lý quá trình khách hàng đặt mua sản phẩm, xác nhận đơn hàng, xử lý thanh toán.
  • Thanh toán (Payment): Xử lý các giao dịch tài chính, xác nhận thanh toán thành công/thất bại.
  • Kho hàng (Inventory): Quản lý số lượng tồn kho, cập nhật khi có đơn hàng mới hoặc nhập hàng.
  • Giao hàng (Shipping): Theo dõi trạng thái vận chuyển, giao hàng cho khách.

👉 Mỗi domain có thể tách thành một microservice riêng biệt, giúp hệ thống dễ mở rộng, bảo trì và phát triển độc lập.


📦 2. Subdomain

👉 Khái niệm:

Subdomain là cách chia nhỏ domain lớn thành các phần nhỏ hơn, mỗi phần giải quyết một khía cạnh cụ thể của nghiệp vụ. Việc phân chia này giúp quản lý phức tạp, xác định đâu là trọng tâm của hệ thống.

✨ Phân loại:

  • Core Subdomain: phần cốt lõi, tạo ra giá trị cạnh tranh cho doanh nghiệp (ví dụ: Đặt hàng, xử lý thanh toán trong TMĐT).
  • Supporting Subdomain: hỗ trợ core, không trực tiếp tạo giá trị nhưng cần thiết (ví dụ: Hệ thống khuyến mãi, gửi email thông báo).
  • Generic Subdomain: nghiệp vụ chung, có thể dùng giải pháp ngoài (ví dụ: Đăng nhập, logging, quản lý người dùng).

🛍️ Ví dụ:

Hệ thống bán hàng online:

  • Core: Thanh toán (Payment) – quyết định doanh thu.
  • Supporting: Đề xuất sản phẩm (Recommendation) – tăng trải nghiệm khách hàng.
  • Generic: Đăng nhập người dùng (Authentication) – có thể dùng dịch vụ ngoài như OAuth, Firebase Auth.

🧱 3. Bounded Context

👉 Khái niệm:

Bounded Context là ranh giới rõ ràng mà bên trong đó các mô hình, ngôn ngữ và logic được định nghĩa một cách nhất quán. Mỗi bounded context có thể có cách hiểu, cách đặt tên, quy tắc nghiệp vụ riêng biệt, không bị lẫn lộn với phần khác của hệ thống.

Việc xác định đúng bounded context giúp tránh xung đột ngữ nghĩa, giảm phức tạp khi tích hợp nhiều hệ thống.

🧠 Mỗi Bounded Context = 1 microservice (thường gặp trong kiến trúc microservices)

🧾 Ví dụ:

  • Ngữ cảnh "Đơn hàng" (Ordering): các khái niệm như Order, OrderItem, OrderStatus chỉ có ý nghĩa trong phạm vi này.
  • Ngữ cảnh "Vận chuyển" (Shipping): các khái niệm như Parcel, DeliveryStatus dùng riêng cho vận chuyển.

Cả hai có thể đều có thuộc tính status, nhưng ý nghĩa và giá trị hoàn toàn khác nhau. Khi tích hợp, cần mapping rõ ràng giữa các context.


🗣️ 4. Ubiquitous Language (Ngôn ngữ phổ quát)

👉 Khái niệm:

Là ngôn ngữ dùng thống nhất giữa lập trình viên và chuyên gia nghiệp vụ. Tất cả thành viên dự án đều sử dụng chung một bộ từ vựng khi trao đổi, viết tài liệu, đặt tên biến/lớp, mô hình hóa nghiệp vụ. Điều này giúp giảm hiểu nhầm, tăng hiệu quả giao tiếp.

🧾 Ví dụ:

Nếu nghiệp vụ gọi là "Đơn hàng" thì trong code nên có:

public class Order { }

Không nên dùng Purchase hoặc Transaction nếu nghiệp vụ không dùng các từ này.

Ứng dụng thực tế: Khi họp với khách hàng, developer và BA đều dùng đúng thuật ngữ, giúp mô hình hóa chính xác, code dễ bảo trì.


🧍 5. Entity (Thực thể)

👉 Khái niệm:

Entity là đối tượng có danh tính (identity) – dù thông tin thuộc tính thay đổi, danh tính vẫn giữ nguyên. Entity thường có một trường ID duy nhất.

📌 Ví dụ:

class Order {
    Guid Id;
    List<OrderItem> Items;
    DateTime CreatedAt;
}

Dù bạn thêm sản phẩm, sửa ngày tạo,... thì Order.Id vẫn giúp bạn nhận diện duy nhất đơn hàng.

Thực tế: Entity thường là các đối tượng chính như User, Order, Product, Customer... trong hệ thống.


🧾 6. Value Object (Đối tượng giá trị)

👉 Khái niệm:

  • Không có danh tính riêng biệt
  • So sánh bằng giá trị các thuộc tính
  • Bất biến (immutable): khi cần thay đổi thì tạo object mới

🔎 Phân biệt với Entity:

  • Entity có ID duy nhất, dùng để nhận diện, dù thuộc tính thay đổi thì ID vẫn giữ nguyên (ví dụ: User, Order).
  • Value Object không có ID, chỉ quan tâm đến giá trị các thuộc tính. Nếu hai value object có cùng giá trị thì coi như giống hệt nhau (ví dụ: hai địa chỉ giống nhau thì là một).

📌 Ví dụ:

class Address {
    string Street;
    string City;
    string ZipCode;
}

Hai Address giống nhau về nội dung là như nhau, không cần ID.

📦 Ứng dụng thực tế:

  • Địa chỉ giao hàng, số điện thoại, email, tiền tệ, khoảng thời gian, màu sắc, kích thước, đơn vị đo lường...
  • Dùng value object giúp code rõ ràng, dễ test, tránh lỗi khi so sánh hoặc truyền dữ liệu.
  • Value object thường được dùng làm thuộc tính của entity (ví dụ: User có Address là value object).

⚡ Lợi ích khi dùng Value Object:

  • Đơn giản hóa logic: Không cần quản lý ID, chỉ cần so sánh giá trị.
  • Tăng tính bất biến: Dễ reasoning, tránh bug do thay đổi ngoài ý muốn.
  • Tái sử dụng: Có thể dùng lại ở nhiều nơi trong hệ thống.
  • Dễ test: So sánh, serialize, validate đơn giản hơn entity.

🚩 Lưu ý khi sử dụng:

  • Không nên dùng value object cho những đối tượng cần theo dõi vòng đời hoặc có trạng thái riêng biệt.
  • Khi dùng làm key trong map/set, cần đảm bảo so sánh giá trị đúng.

🌲 7. Aggregate & Aggregate Root (Tổng hợp & gốc tổng hợp)

👉 Khái niệm:

  • Aggregate: nhóm các entity và value object có liên hệ chặt chẽ, thao tác như 1 đơn vị duy nhất. Aggregate giúp kiểm soát tính nhất quán dữ liệu trong phạm vi nhỏ.
  • Aggregate Root: entity gốc, là điểm duy nhất để truy cập và thay đổi aggregate. Mọi thao tác với các entity con phải thông qua aggregate root.

🧾 Ví dụ:

  • Aggregate: Order gồm nhiều OrderItem, mỗi item là một value object hoặc entity nhỏ.
  • Aggregate Root: Order là gốc, mọi thao tác thêm/xóa/sửa item đều phải qua Order.

Không được sửa OrderItem trực tiếp từ bên ngoài, mà phải qua Order.

order.AddItem(productId, quantity);

Ý nghĩa: Aggregate giúp kiểm soát tính nhất quán nghiệp vụ, tránh lỗi khi nhiều luồng cùng thao tác dữ liệu.


📢 8. Domain Event (Sự kiện miền nghiệp vụ)

👉 Khái niệm:

Là sự kiện phản ánh điều gì đó đã xảy ra trong domain mà nghiệp vụ quan tâm. Domain event giúp hệ thống phản ứng linh hoạt, dễ mở rộng, hỗ trợ tích hợp với các hệ thống khác.

📌 Ví dụ:

  • OrderPlaced: Đơn hàng đã được đặt thành công
  • PaymentSuccessful: Thanh toán thành công
  • InventoryDecreased: Số lượng tồn kho giảm

📡 Trong Microservices:

Các service khác có thể subscribe để phản ứng lại sự kiện, ví dụ gửi email, cập nhật báo cáo, đồng bộ dữ liệu.

📦 Ví dụ gửi sự kiện qua Kafka:

{
  "eventType": "OrderPlaced",
  "orderId": "A123",
  "timestamp": "2025-07-03T10:00:00Z"
}

Thực tế: Domain event giúp tách biệt logic, giảm phụ thuộc giữa các module, dễ mở rộng khi có nghiệp vụ mới.


🏛️ 9. Repository (Kho lưu trữ)

👉 Khái niệm:

Repository là lớp trung gian truy xuất dữ liệu cho aggregate. Giúp domain model không phụ thuộc hạ tầng (DB, SQL, API...), dễ test, dễ thay đổi công nghệ lưu trữ.

public interface IOrderRepository {
    Order GetById(Guid id);
    void Save(Order order);
}

Ứng dụng: Khi muốn đổi từ SQL sang NoSQL, chỉ cần sửa repository, domain model không bị ảnh hưởng.


🔄 10. Application Service (Dịch vụ ứng dụng)

👉 Khái niệm:

Application Service là lớp trung gian điều phối giữa các aggregate, repository, service khác – không chứa logic nghiệp vụ, chỉ orchestration, gọi các hành động nghiệp vụ đúng thứ tự.

📌 Ví dụ:

public class OrderService {
    public void PlaceOrder(PlaceOrderRequest request) {
        var order = new Order(request.CustomerId);
        _orderRepository.Save(order);
        _eventBus.Publish(new OrderPlaced(order.Id));
    }
}

Ý nghĩa: Application Service giúp tách biệt logic điều phối (workflow) với logic nghiệp vụ (domain), code rõ ràng, dễ test, dễ mở rộng.