Model Microservices — DDD & Bounded Context (Building Microservices)

Phong

Mở đầu

Ảnh: Daniil Komov — Pexels

Khi bắt tay vào xây dựng một hệ thống microservices, câu hỏi đầu tiên và cũng là khó nhất chính là: làm sao để xác định ranh giới giữa các service? Chia nhỏ một monolithic application thành microservices không đơn thuần là chuyện kỹ thuật — nó đòi hỏi một cách tiếp cận có hệ thống để đảm bảo mỗi service có một phạm vi trách nhiệm rõ ràng, độc lập, và có thể phát triển riêng rẽ.

Chapter 2 của Building Microservices (Sam Newman, 2nd Edition) dành toàn bộ nội dung để trả lời câu hỏi đó, với công cụ mạnh nhất là Domain-Driven Design (DDD) và khái niệm trung tâm: Bounded Context.

Domain-Driven Design — Không chỉ là code, mà là ngôn ngữ chung

Ảnh: ThisIsEngineering — Pexels

Eric Evans giới thiệu DDD lần đầu năm 2003, nhưng phải đến kỷ nguyên microservices, nó mới thực sự bùng nổ. Lý do rất đơn giản: DDD cung cấp một bộ công cụ để ánh xạ thế giới thực (business domain) vào code, và microservices cần chính xác điều đó để xác định ranh giới.

Sam Newman nhấn mạnh: DDD giúp bạn trả lời câu hỏi "service nào làm gì?" thay vì để các quyết định kỹ thuật thuần tuý (như "dùng database nào", "dùng queue gì") dẫn dắt kiến trúc. Cốt lõi của DDD gồm:

  • Ubiquitous Language — Ngôn ngữ chung giữa domain expert và developer. Mỗi thuật ngữ trong code phải khớp với thuật ngữ business dùng hàng ngày.
  • Bounded Context — Ranh giới rõ ràng trong đó một ubiquitous language có hiệu lực.
  • Aggregate — Nhóm các entity được xử lý như một đơn vị consistency.
  • Entity vs Value Object — Phân biệt đối tượng có danh tính (identity) và đối tượng chỉ mang giá trị.

Bounded Context — Trái tim của việc xác định service boundary

Ảnh: Jan van der Wolf — Pexels

Bounded Context là ranh giới logic trong đó một model (ubiquitous language) cụ thể có ý nghĩa. Một thuật ngữ có thể mang nghĩa khác nhau ở các context khác nhau — ví dụ "Order" trong context "Bán hàng" khác với "Order" trong context "Vận chuyển". Mỗi context có model riêng, database riêng (nếu cần), và giao tiếp qua các interface được định nghĩa rõ.

Điều này mapping trực tiếp vào microservices: mỗi microservice nên là một bounded context. Nếu bạn có một service "Order" xử lý cả thanh toán, vận chuyển, và inventory, thì bạn đã phá vỡ bounded context — đây là dấu hiệu của "low-cohesion service" hay nói cách khác là distributed monolith.

Newman đưa ra một số kỹ thuật để khám phá bounded context trong thực tế:

  • Event Storming — Workshop tập trung, dùng sticky notes để map toàn bộ business event, từ đó phát hiện aggregate và bounded context tự nhiên.
  • Domain Storytelling — Kể chuyện theo luồng nghiệp vụ, ghi nhận từng bước và role, rồi nhóm chúng lại.
  • Phân tích danh từ/động từ — Trong requirement document, danh từ thường là entity/aggregate, động từ là hành vi. Cụm danh từ lặp đi lặp lại trong cùng ngữ cảnh là dấu hiệu của một bounded context.

Aggregate — Đơn vị consistency trong từng service

Một khái niệm DDD quan trọng không kém là Aggregate. Aggregate là một nhóm các đối tượng được xử lý như một đơn vị — mọi thay đổi bên trong aggregate phải được đảm bảo consistency. Aggregate được truy cập qua một "root" duy nhất (Aggregate Root) và bên ngoài không được tham chiếu trực tiếp vào entity bên trong.

Ví dụ: "Order" là aggregate root, bên trong nó có "OrderItem" (entity con) và "ShippingAddress" (value object). Bạn không thể thêm OrderItem vào Order mà không đi qua Order root — điều này đảm bảo tính toàn vẹn dữ liệu.

Newman nhấn mạnh: Aggregate là natural unit cho một microservice. Mỗi aggregate nên là một service, hoặc nếu aggregate quá nhỏ, vài aggregate liên quan có thể gộp thành một service. Nếu một aggregate quá lớn, đó là dấu hiệu bạn cần chia nhỏ bounded context.

Từ DDD đến Microservices — Mối quan hệ mapping

Mối quan hệ giữa DDD concepts và microservices có thể tóm tắt như sau:

  • Bounded Context → Microservice — Mỗi bounded context là một service riêng biệt.
  • Ubiquitous Language → API contract — Ngôn ngữ chung của context chính là ngôn ngữ trong API của service đó.
  • Aggregate → Data ownership — Aggregate quyết định dữ liệu nào thuộc về service nào.
  • Context Map → Service dependency graph — Bản đồ các bounded context và quan hệ giữa chúng (Customer/Supplier, Shared Kernel, Conformist, Anti-Corruption Layer, v.v.)

Newman dành thời gian phân tích các dạng Context Map relationships vì chúng quyết định cách các service giao tiếp với nhau — điều này ảnh hưởng trực tiếp đến việc chọn protocol (REST, gRPC, event-driven) và mức độ coupling giữa các team.

Key Takeaways

  • DDD là công cụ chiến lược để xác định ranh giới service, không chỉ là pattern thiết kế code.
  • Mỗi microservice nên là một bounded context — nếu không, bạn đang tạo distributed monolith.
  • Aggregate là đơn vị consistency tự nhiên cho service boundary.
  • Ubiquitous Language giúp domain expert và developer nói cùng một ngôn ngữ, giảm misunderstanding.
  • Context Map cho thấy bức tranh tổng thể về cách các service liên quan và phụ thuộc lẫn nhau.
  • Event Storming và Domain Storytelling là hai kỹ thuật thực hành mạnh để khám phá bounded context.

Glossary

TermÝ nghĩa
Domain-Driven DesignPhương pháp thiết kế phần mềm lấy business domain làm trung tâm, do Eric Evans đề xướng
Bounded ContextRanh giới logic trong đó một model/ubiquitous language cụ thể có hiệu lực
Ubiquitous LanguageNgôn ngữ chung giữa domain expert và developer, phản ánh trong code lẫn conversation
AggregateNhóm entity được xử lý như một đơn vị consistency, truy cập qua Aggregate Root
Aggregate RootEntity duy nhất dùng để truy cập và thay đổi aggregate
EntityĐối tượng có danh tính (identity) riêng biệt, theo dõi được qua vòng đời
Value ObjectĐối tượng chỉ mang giá trị, không có identity, bất biến (immutable)
Context MapBản đồ các bounded context và quan hệ giữa chúng
Event StormingWorkshop kỹ thuật dùng sticky notes để khám phá domain event và bounded context
Anti-Corruption LayerLớp chuyển đổi giữa hai bounded context để ngăn model này làm hỏng model kia

Kết

Chapter 2 đặt nền móng quan trọng: kiến trúc microservices không bắt đầu từ công nghệ, mà bắt đầu từ cách bạn hiểu và mô hình hóa business domain. DDD không phải là "điều kiện cần" để làm microservices — bạn hoàn toàn có thể xây microservices mà không cần DDD — nhưng nếu thiếu nó, ranh giới service sẽ được quyết định bởi cảm tính hoặc may rủi, dẫn đến một hệ thống khó bảo trì về lâu dài.

Chapter 3 sẽ đi vào chi tiết về cách các service giao tiếp với nhau — từ đồng bộ (REST, gRPC) đến bất đồng bộ (message queue, event-driven). Đây là phần mà hầu hết developer bắt đầu khi nghĩ về microservices, nhưng sau chapter này bạn đã hiểu: giao tiếp chỉ là phương tiện, còn ranh giới service mới là quyết định kiến trúc cốt lõi.