DDD & Bounded Context — Tách Microservices (Building Microservices)
Mở đầu
Ảnh: Christina Morillo — Pexels
Một trong những câu hỏi khó nhất khi chuyển sang microservices là: "Cắt cái monolith này ra chỗ nào?" Cắt sai chỗ là hậu quả kéo dài — service quá to hoá ra "distributed big ball of mud", service quá nhỏ thì chìm trong orchestration code.
Sam Newman dành hẳn Chương 2 của Building Microservices để trả lời câu hỏi đó. Và câu trả lời của ông là: Domain-Driven Design (DDD). Cụ thể hơn là bounded context — ranh giới tự nhiên của một domain model.
Nếu bạn chưa đọc cuốn "Domain-Driven Design" của Eric Evans thì đừng lo — chapter này tóm gọn đủ để bạn áp dụng ngay vào thiết kế microservices.
Domain-Driven Design — Tư duy lấy domain làm trung tâm
Ảnh: cang hai — Pexels
DDD ra đời từ năm 2003, nhưng phải tới kỷ nguyên microservices nó mới thực sự phát huy sức mạnh. Tinh thần cốt lõi: code phải phản ánh domain business, không phải technical concerns.
Trong DDD có hai lớp concepts:
- Strategic Design: Cách chia nhỏ hệ thống thành các bounded context, xác định mối quan hệ giữa chúng — đây là phần bạn cần nhất khi modelling microservices.
- Tactical Design: Các building blocks bên trong một bounded context: Entity, Value Object, Aggregate, Repository, Domain Event, Service.
Newman tập trung vào strategic design vì nó quyết định cấu trúc microservices của bạn — còn tactical là chuyện implement bên trong từng service.
Bounded Context — Ranh giới của mỗi microservice
Một bounded context là ranh giới logic trong đó một domain model có nghĩa. Ngoài ranh giới đó, cùng một thuật ngữ có thể mang ý nghĩa hoàn toàn khác.
Ví dụ kinh điển: Trong hệ thống e-commerce, khái niệm "Order" (đơn hàng) ở context Sales khác với "Order" ở context Shipping:
- Sales context: Order có items, total, discount, payment status
- Shipping context: Order có weight, dimensions, delivery address, tracking number
Ép chung hai Order này vào một class là bắt đầu cho tight coupling. Thay vào đó, mỗi bounded context có Order riêng — chỉ chứa field cần thiết. Đây là lý do bounded context trở thành natural boundary cho microservices.
Ubiquitous Language — Ngôn ngữ chung giữa dev và domain expert
DDD yêu cầu xây dựng một ubiquitous language — ngôn ngữ thống nhất dùng bởi cả developer và business stakeholders. Mỗi bounded context có ubiquitous language riêng.
Thực tế bạn sẽ thấy:
- Dev gọi "line item", business gọi "món hàng" → confusion
- Code dùng "CustomerRepository.GetActiveCustomers()" nhưng business hỏi "khách mới tuần này" → gap
Ubiquitous language buộc cả hai bên thống nhất thuật ngữ và đưa nó vào code, vào API contract, vào documentation. Không có nó, ranh giới context rất dễ bị mờ nhạt.
Aggregate — ranh giới consistency
Ảnh: panumas nikhomkhai — Pexels
Bên trong mỗi bounded context, bạn có Aggregates — nhóm các đối tượng được coi như một đơn vị nhất quán dữ liệu.
Ví dụ: Order với OrderItems. Một Aggregate Order chứa các OrderItems, và khi bạn thay đổi OrderItem, bạn phải load toàn bộ Aggregate để đảm bảo invariant:
- Tổng tiền = sum(items) × discount
- Không cho phép order quá 100 items
Aggregate là transaction boundary — mọi thao tác trong một aggregate đều ACID. Trong microservices, mỗi transaction chỉ nên ảnh hưởng tới một aggregate duy nhất. Đây là lý do việc thiết kế aggregate size quyết định performance của hệ thống.
Domain Events — giao tiếp giữa các bounded context
Khi một bounded context cần thông báo điều gì đó cho context khác (mà không tight coupling), dùng Domain Events.
Ví dụ: Khi order được confirmed (Sales context), emit event OrderConfirmed. Shipping context subscribe event này, tạo shipment tương ứng. Hai context không gọi API trực tiếp của nhau — chỉ trao đổi qua events.
Đây là nền tảng cho eventual consistency trong microservices và là pattern Newman khuyên dùng xuyên suốt cuốn sách.
Context Mapping — bản đồ quan hệ giữa các contexts
Không phải bounded context nào cũng độc lập tuyệt đối. Chúng có quan hệ với nhau. DDD định nghĩa vài kiểu quan hệ (context map):
- Partnership: Hai contexts hợp tác chặt — thay đổi của bên này ảnh hưởng bên kia
- Shared Kernel: Chia sẻ một phần model chung (ít khuyến khích trong microservices vì tạo coupling)
- Customer-Supplier: Một context (upstream) cung cấp dữ liệu cho context kia (downstream)
- Conformist: Downstream chấp nhận model của upstream (đơn giản nhưng mất autonomy)
- Anti-Corruption Layer: Tầng chuyển đổi model giữa hai contexts — bảo vệ domain model khỏi bị "nhiễm" từ bên ngoài
- Open Host Service: Context publish API chuẩn cho context khác dùng (VD: REST API)
Khi thiết kế microservices, bạn vẽ context map trước để thấy service nào phụ thuộc service nào, và chọn pattern giao tiếp phù hợp.
Key Takeaways
- Bounded context = microservice boundary. Mỗi microservice là một bounded context — không chia nhỏ hơn, không gộp bừa bãi.
- Ubiquitous language là nhiệt kế. Nếu dev và business dùng từ khác nhau cho cùng concept, bounded context của bạn đang sai.
- Aggregate = transaction boundary. Transaction không vượt quá một aggregate. Thiết kế aggregate size ảnh hưởng trực tiếp đến consistency model.
- Domain Events cho giao tiếp giữa services. Event-driven là cách giữ autonomy cho mỗi bounded context.
- Vẽ context map trước khi code. Biết trước quan hệ giữa các services giúp chọn pattern giao tiếp — sync (REST/gRPC) hay async (events).
Kết
Chương 2 của Building Microservices không dạy bạn DDD từ A đến Z, nhưng nó cho bạn đủ công cụ để trả lời câu hỏi: "Cắt monolith ở đâu?" — bằng cách tìm bounded contexts, xác định ubiquitous language, và vẽ context map.
Sau khi đã xác định được ranh giới services, câu hỏi tiếp theo là: làm sao cắt monolith cũ thành các service đó? Đó là nội dung của Chương 3 — Splitting the Monolith, sẽ được đề cập ở bài sau.
📋 Phụ lục thuật ngữ
- Bounded Context — ranh giới logic của một domain model; mỗi bounded context có ubiquitous language riêng
- Ubiquitous Language — ngôn ngữ thống nhất giữa dev và domain expert trong một bounded context
- Aggregate — nhóm đối tượng được coi là một đơn vị nhất quán dữ liệu; transaction boundary
- Domain Event — sự kiện domain xảy ra trong một bounded context, các context khác có thể subscribe
- Context Map — bản đồ mô tả quan hệ giữa các bounded contexts
- Anti-Corruption Layer — tầng chuyển đổi model giữa hai contexts, ngăn domain model bị nhiễm từ bên ngoài
- Strategic Design — cấp độ DDD về chia nhỏ hệ thống và xác định ranh giới context
- Tactical Design — cấp độ DDD về building blocks (Entity, Value Object, Aggregate...) bên trong một context