Đây là chương đặc biệt quan trọng trong kiến trúc DDD vì nó mở rộng từ nội tại một Bounded Context sang giao tiếp giữa nhiều Bounded Context – một thách thức phổ biến trong hệ thống phân tán hiện đại.
Xem lại phần 8 tại: DDD: Phần 8 – Các pattern kiến trúc trong Domain-Driven Design
Nội dung sẽ chia làm 5 phần nhỏ:
Phần | Nội dung chính |
---|---|
1 | Model Translation – Stateless |
2 | Model Translation – Stateful |
3 | Outbox Pattern – đảm bảo publish event đáng tin cậy |
4 | Saga Pattern – quy trình nghiệp vụ đa aggregate |
5 | Process Manager – quy trình phức tạp có điều kiện |
🧠 Phần 1: Model Translation – Dịch mô hình giữa các bounded context
🔄 Tại sao cần dịch mô hình (Model Translation)?
- Mỗi bounded context định nghĩa mô hình nghiệp vụ riêng, sử dụng ngôn ngữ chung (ubiquitous language) riêng.
- Khi nhiều context phải tương tác, mô hình của context này thường không tương thích với context khác.
➕ Nếu có thể hợp tác giữa các team: dùng Partnership hoặc Shared Kernel.
➖ Nếu không thể hợp tác chặt chẽ: cần có lớp dịch (translation layer) giữa hai context.
🧱 Các mẫu dịch mô hình:
Tình huống | Giải pháp |
---|---|
Downstream không dùng được model của upstream | Dùng Anticorruption Layer (ACL) |
Upstream muốn bảo vệ internal model | Dùng Open Host Service (OHS) |
Nhiều bên cần dùng mô hình đã dịch | Tạo Interchange Context (một Bounded Context chuyên để chuyển đổi mô hình) |
⚙️ Hai kiểu dịch mô hình:
- Stateless Translation – không cần lưu trạng thái, dịch “ngay tại chỗ”
- Stateful Translation – dịch phức tạp hơn, cần lưu trạng thái và đôi khi là lưu trữ trung gian
📬 Stateless Translation
🔁 Dịch qua Proxy (Proxy Pattern)
- Synchronous communication (giao tiếp đồng bộ): dịch inline trong mã hoặc tại API Gateway (như Kong, KrakenD, AWS API Gateway…).
- Dùng để chuyển đổi giữa mô hình internal và published language.
- Cho phép xuất bản nhiều version của API cùng lúc.
- Asynchronous communication (giao tiếp bất đồng bộ): dùng message proxy để intercept và transform các message → gửi đến consumer.

❗ Quan trọng: không nên public domain events “raw” → hãy dịch thành published language để ẩn đi chi tiết nội bộ!
📦 Interchange Context
- Khi một bounded context chỉ làm nhiệm vụ dịch mô hình cho nhiều nơi khác → gọi là interchange context.
- Đây là một bounded context thực thụ (real), với logic chuyển đổi riêng biệt.
💡 Notes nhanh:
Khái niệm | Vai trò |
---|---|
ACL | Giúp downstream “dịch” mô hình của upstream sang định dạng riêng |
OHS | Upstream công khai API theo published language, bảo vệ mô hình nội bộ |
API Gateway | Có thể dùng để dịch API layer mà không cần viết mã trong codebase. Dùng trong Routing, Aggregation, Auth, Throttling, Logging,… |
Message Proxy | Dùng cho async messaging, dịch sự kiện gửi đến |
Interchange Context | Một bounded context chuyên để xử lý translation |
🧠 Phần 2: Model Translation có trạng thái (Stateful Translation)
🤔 Khi nào cần dịch có trạng thái?
Khi translation phức tạp hơn việc “map trực tiếp” giữa hai object:
- Phải tổng hợp (aggregate) nhiều dữ liệu đến → để batch xử lý.
- Phải gộp thông tin từ nhiều nguồn (multiple upstreams).
- Phải phục vụ UI hoặc API front-end cần “tổng hợp” model (VD: Backend-for-Frontend pattern).
📦 1. Batching / Aggregation

Ví dụ:
- Một context nhận nhiều yêu cầu nhỏ rải rác trong thời gian ngắn → cần gom lại để xử lý batch.
- Hoặc cần gom nhiều message nhỏ → thành một message lớn gửi tiếp đi (ví dụ như hình).
➡️ Phải dùng persistent storage để ghi lại trạng thái nhận được → khi đủ điều kiện thì mới thực hiện hành động tiếp theo.
📌 Không thể dùng API Gateway cho kiểu này → vì gateway không lưu trữ được trạng thái trung gian.
⚙️ 2. Kết hợp nhiều nguồn (Multiple Source Aggregation)
Đặc biệt dùng trong mô hình Backend for Frontend (BFF):
- UI cần dữ liệu từ nhiều context → BFF đóng vai trò aggregate và expose endpoint phù hợp cho client.
💡 Có thể tách logic tích hợp này ra thành một Anticorruption Layer phía trước bounded context chính, để tách biệt xử lý giao tiếp và xử lý nghiệp vụ.

🛠️ Triển khai thực tế:
- Có thể tự viết hệ thống lưu trạng thái chuyển đổi.
- Hoặc dùng tools/platform:
- Stream Processing: Kafka Streams, AWS Kinesis, Apache Flink…
- Batching Tools: Apache NiFi, AWS Glue, Spark…
📌 Tóm tắt nhanh:
Tình huống | Cần stateful translation? |
---|---|
Dịch đơn giản, 1:1 | ❌ |
Gom nhiều request thành 1 | ✅ |
Tổng hợp từ nhiều nguồn (BFF) | ✅ |
Dịch dùng event/message queue | ✅ nếu cần nhớ trạng thái để phản ứng sau |
Dùng API Gateway | ❌ không đủ khả năng |
💡 Ứng dụng thực tế:
Trong hệ thống Microservices:
- UI cần hiển thị dashboard → BFF context aggregate nhiều service → dùng stateful ACL.
- Hệ thống nhận nhiều sự kiện từ thiết bị IoT → gom thành batch xử lý định kỳ → dùng batching aggregator.
🔄 Bối cảnh:
- Các Aggregate trong DDD thường không trực tiếp gọi các thành phần bên ngoài.
- Thay vào đó, chúng phát ra domain events → thành phần khác (subscriber) lắng nghe và phản ứng.
- Nhưng publish domain events như thế nào cho đúng?
❌ Sai lầm phổ biến: Publish ngay trong Aggregate
🔥 Ví dụ sai:
public void Deactivate(string reason) { ... var newEvent = new CampaignDeactivated(_id, reason); _events.Append(newEvent); _messageBus.Publish(newEvent); // ❌ Gửi luôn sự kiện }
😱 Hệ quả:
- Không nhất quán trạng thái:
- Event có thể đến subscriber trước khi Aggregate được commit vào database.
- Subscriber nhận được
"CampaignDeactivated"
→ nhưng DB vẫn chưa thay đổi!
- Rủi ro rollback không đồng bộ:
- Nếu commit DB thất bại (do race condition, lỗi logic, hoặc hạ tầng) → event đã được publish không thể thu hồi.
❌ Giải pháp thứ hai – Chuyển việc publish sang Application Layer
🧪 Ví dụ:
var campaign = repository.Load(id); campaign.Deactivate(reason); repository.CommitChanges(campaign); // ✅ Commit DB trước var events = campaign.GetUnpublishedEvents(); // ✅ Lấy các event chưa gửi foreach (var e in events) { _messageBus.Publish(e); // ❌ Publish sau commit } campaign.ClearUnpublishedEvents();
😬 Vấn đề:
- Nếu message bus gặp sự cố hoặc server chết sau commit DB nhưng chưa publish → Event sẽ không được gửi, hệ thống không nhất quán.
✅ Giải pháp đúng: Dùng Outbox Pattern
🧠 Phần 3: Outbox Pattern – Đảm bảo phát tán sự kiện đáng tin cậy
💣 Vấn đề thường gặp: Publish sự kiện trước khi commit DB
Sai lầm phổ biến:
_event.Append(newEvent); _messageBus.Publish(newEvent);
➡️ Nếu DB chưa commit mà đã publish → dữ liệu và sự kiện không khớp
➡️ Nếu transaction thất bại nhưng event đã được gửi đi → sự kiện “ma” xảy ra, không thể thu hồi.
✅ Giải pháp: Outbox Pattern
📌 Cách hoạt động:

- Aggregate thực hiện hành động → sinh event.
- Ghi event vào một bảng outbox riêng, trong cùng transaction với DB chính.
- Một relay process sẽ:
- Lấy event từ outbox
- Gửi lên message bus
- Đánh dấu là đã gửi hoặc xóa

🧾 Dữ liệu mẫu:
{ "campaign-id": "...", "state": { "publishing-state": "DEACTIVATED" }, "outbox": [ { "type": "campaign-deactivated", "reason": "Goals met", "published": false } ] }
➡️ outbox
chứa các sự kiện chưa được gửi. Khi relay chạy, nó sẽ gửi đi và cập nhật trạng thái.
🔁 Cơ chế gửi sự kiện:
Cách | Mô tả |
---|---|
Pull | Relay liên tục query bảng outbox tìm sự kiện mới |
Push | Dựa trên database trigger / stream / change feed (ví dụ: DynamoDB Streams, Postgres Logical Decoding) để báo cho relay |
➡️ Dù dùng cách nào thì outbox vẫn chỉ đảm bảo “at-least-once” delivery.
💥 Tình huống khó:
- Nếu relay gửi thành công, nhưng chết trước khi update DB → sự kiện bị gửi lặp lại.
- Do đó, subscriber cần xử lý idempotency (đảm bảo xử lý lại cũng không ảnh hưởng).
📌 Tổng kết nhanh:
Thành phần | Vai trò |
---|---|
Outbox table | Lưu trữ sự kiện trong cùng transaction |
Message relay | Gửi sự kiện từ DB đến message bus |
At-least-once | Có thể gửi lại, cần xử lý trùng lặp |
Không nên publish trong Aggregate | Vì có thể gây inconsistency nếu DB rollback |
💡 Gợi ý áp dụng:
- Trong các hệ thống microservices có event sourcing hoặc event propagation, luôn nên có outbox.
- Có thể triển khai với các tools như:
- Debezium (tailing transaction logs)
- Kafka Connect
- Custom poller chạy mỗi x giây
🧠 Phần 4: Saga Pattern – Quản lý quy trình nhiều Aggregate
📌 Vấn đề:
Trong DDD, ta giới hạn mỗi transaction trong 1 aggregate để đảm bảo tính nhất quán nội bộ.
❓ Nhưng nếu business process yêu cầu phối hợp nhiều aggregate thì sao?
➡️ Saga pattern ra đời để giải quyết:
Một long-running business process gồm nhiều bước nhỏ, mỗi bước là một transaction riêng biệt.
🧭 Cách hoạt động:
- Saga lắng nghe các domain event, và gửi command tương ứng đến các bounded context khác.
- Nếu có bước nào thất bại, saga sẽ gửi command bù trừ (compensating action) để rollback logic nghiệp vụ.
📑 Ví dụ: Quảng cáo được duyệt
CampaignActivated
→ saga nhận được- Saga gửi
SubmitAdvertisement
đếnAdPublishing
- Nhận
PublishingConfirmed
→ saga gửiTrackPublishingConfirmation
- Nếu nhận
PublishingRejected
→ saga gửiTrackPublishingRejection
➡️ Saga không chứa trạng thái, chỉ lắng nghe và phản ứng.
🧠 Quan sát quan trọng:
❗ Một saga không đồng nghĩa với việc xử lý tất cả mọi logic trong cùng một aggregate.
Nó kết nối nhiều aggregate với nhau theo logic business chứ không gộp chúng lại.
🔁 Saga nâng cao: Event-Sourced Saga
Khi cần ghi nhớ trạng thái (ai đã phản hồi, đã gửi gì chưa…), saga có thể được thiết kế như một event-sourced aggregate:
- Nhận event → sinh
CommandIssuedEvent
(không chạy command trực tiếp) - Các command sẽ được outbox relay xử lý
📌 Mục tiêu: tách việc cập nhật trạng thái khỏi việc gọi hành động
✅ Ưu điểm của Saga:
Điểm mạnh | Lợi ích |
---|---|
Không cần distributed transaction | Mỗi bước là một transaction riêng biệt |
Có thể rollback bằng logic nghiệp vụ | Dễ dàng phục hồi trạng thái nếu lỗi |
Rất phù hợp với event-driven system | Cấu trúc rõ ràng và tách biệt |
Hỗ trợ nhất quán cuối cùng (eventual consistency) | Đúng với nguyên lý DDD |
⚠️ Lưu ý khi dùng Saga:
- Không dùng Saga để “vá lỗi” thiết kế aggregate sai.
- Nếu nghiệp vụ cần tính nhất quán tuyệt đối, hãy xem lại cách define aggregate boundary.
💡 Gợi ý ứng dụng:
- E-commerce: thanh toán, đặt hàng, cập nhật kho
- Booking system: vé máy bay, khách sạn, hoàn tiền nếu lỗi
- Quy trình duyệt tài liệu: nhiều bước phê duyệt, có thể hồi lại
🧠 Phần 5: Process Manager – Điều phối quy trình nghiệp vụ phức tạp có trạng thái
🤔 Khác gì với Saga?
Saga | Process Manager |
---|---|
Phản ứng (reactive): nhận event rồi phát lệnh | Chủ động điều phối toàn bộ quá trình |
Thường là luồng đơn giản, tuyến tính | Có logic rẽ nhánh (if-else), vòng lặp, điều kiện |
Được kích hoạt bởi một event | Phải được khởi tạo rõ ràng như một thực thể (Process) |
Thường không cần lưu state | Cần lưu trạng thái, có ID, tồn tại dài hạn |
📌 Rule of thumb:
Nếu có
if-else
trong saga → đó là process manager 😄
📑 Ví dụ: Đặt chuyến công tác
Quy trình gồm nhiều bước:
- Tính toán tuyến bay → gửi cho nhân viên duyệt
- Nếu từ chối → gửi cho quản lý phê duyệt lại
- Khi OK → đặt vé
- Tiếp theo → đặt khách sạn
- Nếu khách sạn full → huỷ vé máy bay
🎯 Đây là quy trình phức tạp, không có một event duy nhất khởi tạo, cần lưu trạng thái → phải dùng Process Manager.
🏗️ Triển khai:
- Process manager được triển khai như một aggregate:
- Có
ID
,state
,event store
hoặcsnapshot
- Nhận event → sinh
CommandIssuedEvent
→ gửi đi bằng outbox
- Có
Ví dụ:
public class BookingProcessManager { private Destination _destination; private Route _route; private IList<Route> _rejectedRoutes; public void Initialize(...) {...} public void Process(RouteConfirmed evt) {...} public void Process(RouteRejected evt) {...} ... }
➡️ Lưu trữ các bước đã xử lý, các lựa chọn bị từ chối, quyết định tiếp theo.
💡 Khi nào nên dùng Process Manager?
Dấu hiệu | Hành động |
---|---|
Nhiều bước, có điều kiện rẽ nhánh | ✔ Dùng process manager |
Cần lưu trạng thái giữa các bước | ✔ Dùng process manager |
Quy trình phức tạp, kéo dài (ngày, tuần) | ✔ Dùng process manager |
Chỉ có 1-2 bước đơn giản | ❌ Dùng saga là đủ |
📌 Tổng kết so sánh:
Tiêu chí | Saga | Process Manager |
---|---|---|
Phạm vi | Gọn, tuyến tính | Linh hoạt, có điều kiện |
State | Thường không cần | Bắt buộc cần lưu |
Khởi tạo | Tự động từ event | Phải tạo mới qua command |
Logic | Reactive | Chủ động điều phối |
💡 Gợi ý áp dụng:
- Ngân hàng: duyệt tín dụng qua nhiều cấp, có điều kiện
- Du lịch: đặt combo bay + khách sạn + visa
- Quản lý hợp đồng: từ đề nghị, duyệt, ký, triển khai → mỗi bước là event → manager điều phối
🧪 Ôn lại: Câu hỏi cuối chương
- Pattern nào yêu cầu dịch mô hình?
✅ Anticorruption Layer
✅ Open-Host Service - Mục tiêu của Outbox pattern?
✅ Đảm bảo publish message đáng tin cậy - Outbox chỉ dùng để publish message?
❌ Không → còn dùng cho bất kỳ loại side-effect nào cần đảm bảo đồng bộ với DB - Saga và Process Manager khác nhau gì?
✅ Saga kích hoạt bởi event; PM cần khởi tạo rõ ràng
✅ PM dùng cho luồng phức tạp có logic
❌ Không đúng: “Saga bắt buộc dùng event sourcing” – KHÔNG bắt buộc
❌ Không đúng: “PM không bao giờ cần lưu state” – thực tế PM luôn cần lưu state
Để lại một bình luận
Bạn phải đăng nhập để gửi bình luận.