Năm 2004, một công ty payroll phải bỏ ra hàng triệu đô để sửa lỗi từ đoạn code này:
// Java
public Money regularHours() {
return this.hours.regularHours();
}
Không có bug logic nào. Compiler không warning. Unit test xanh hết.
Vấn đề là: team Kế toán và team HR đều gọi hàm regularHours() — nhưng với hai định nghĩa hoàn toàn khác nhau. Kế toán cần số giờ theo quy định của CFO. HR cần số giờ theo quy định của COO. Developer sửa logic theo yêu cầu CFO mà không biết HR đang phụ thuộc vào cùng hàm đó. Báo cáo HR sai im lặng nhiều tháng cho đến khi phát hiện.
Đây không phải lỗi code. Đây là lỗi domain model.
Domain Discovery Đang Bị Hiểu Sai
Hầu hết team hiểu Domain Discovery là giai đoạn “thu thập yêu cầu”: họp kickoff, viết user stories, vẽ ERD, làm UML class diagram, rồi chuyển sang code.
Output điển hình: một đống tài liệu mà client không đọc, developer không nhớ, và domain expert không nhận ra hệ thống của mình trong đó.
Vấn đề với cách tiếp cận này: static documents capture được WHAT, nhưng không capture được HOW things change và WHEN context shifts. Và chính những thứ thay đổi theo thời gian, theo context, theo actor mới là thứ quyết định service boundary.
Vấn Đề Thực Sự: Semantic Misalignment
Ba ví dụ thực tế về cách một cái tên sai phá hủy hệ thống:
1. Sprite OS — “Block” Bug
Trong hệ điều hành phân tán Sprite, từ block được dùng cho cả file block (phân chia logic của file) lẫn disk block (sector vật lý trên đĩa). Một developer dùng disk-block index ở chỗ cần file-block index. Kết quả: data loss ngẫu nhiên, block đột nhiên toàn số 0. Bug mất nhiều năm mới tìm ra.
2. Payroll — “Regular Hours”
Hai teams dùng cùng hàm regularHours() với hai nghĩa khác nhau trong cùng class Employee. Không có gì ngăn developer sửa mà không biết impact. Chi phí: hàng triệu đô.
3. Taxi App — “Kitty Problem”
Một startup taxi split microservices theo functional behavior: FinderService, SelectorService, DispatcherService — tất cả share cùng data record “Order”. Khi business thêm tính năng “giao mèo con”, định nghĩa Order phải thay đổi và tất cả services phải deploy lại cùng lúc. Independent deployability: không tồn tại.
Cả ba trường hợp có cùng root cause: cùng từ, khác mental model, không ai biết ranh giới ở đâu.
Domain Discovery Thực Sự Là Gì
Domain Discovery không phải thu thập yêu cầu. Đó là quá trình khám phá hành vi của hệ thống, luồng dữ liệu, và điểm mà context thay đổi.
Ba câu hỏi cần trả lời:
- Điều gì xảy ra trong hệ thống này? (Events — những sự kiện đã xảy ra, immutable facts)
- Ai trigger điều đó? (Actors + Commands — intent, chưa được validate)
- Ở đâu thì “Order” của context này khác “Order” của context kia? (Pivot points — nơi Bounded Context thay đổi)
Khi bạn trả lời được ba câu này một cách cụ thể, bạn bắt đầu nhìn thấy kiến trúc — không phải từ trên xuống, mà từ hành vi thực tế lên.
Bounded Context và Ubiquitous Language — Làm Rõ Hai Khái Niệm
Bounded Context ≠ Microservice
Bounded Context là ranh giới logic — nơi một domain model cụ thể có giá trị. Microservice là đơn vị deployment vật lý.
Bạn có thể có 3 Bounded Context trong cùng một monolith — vẫn là thiết kế tốt nếu có internal module boundaries rõ ràng. Ngược lại, 10 microservices không có Bounded Context rõ ràng = distributed monolith.
Ví dụ với từ “Order”:
- Sales context: Order = đơn hàng chưa thanh toán, có thể hủy
- Fulfillment context: Order = yêu cầu xử lý kho, không thể hủy
- Shipping context: Order = kiện hàng vật lý cần giao
Ba Bounded Context. Ba objects hoàn toàn khác nhau. Không phải ba trạng thái của cùng một object.
Ubiquitous Language ≠ Glossary
Glossary là danh sách định nghĩa từ — hữu ích nhưng passive. Ubiquitous Language là mental model chung mà cả team (developer + domain expert + business) đều dùng để suy nghĩ và nói chuyện về hệ thống.
Sự khác biệt thực tế: khi developer đặt tên class OrderRepository thay vì Orders, họ đang dùng ngôn ngữ implementation — không phải ngôn ngữ domain. Ubiquitous Language yêu cầu code phải “nói chuyện” bằng ngôn ngữ của business, không phải ngôn ngữ của framework.
EventStorming: Cơ Chế Bên Trong
EventStorming là phương pháp domain discovery phổ biến nhất hiện nay, được Alberto Brandolini phát triển. Đây là cách nó hoạt động thực sự:
Bước 1 — Silent Ideation
Mỗi người viết Domain Events lên sticky notes màu cam — im lặng, không thảo luận. Mục đích: tránh “vocal dominance” — người nói to nhất không được định hình mental model của cả nhóm.
Domain Events luôn ở dạng past tense, là immutable facts: OrderPlaced, PaymentFailed, InventoryReserved. Không phải “place order” hay “payment”.
Bước 2 — Thêm Commands (màu xanh dương)
Commands là intent chưa được validate: SubmitOrder, ProcessPayment. Khác với Event ở chỗ Command có thể fail — nó chưa xảy ra, chỉ là yêu cầu. Mỗi Command dẫn đến một Event (nếu thành công). Chuỗi Command → Event → Command → Event bắt đầu lộ ra flow thực tế của hệ thống.
Bước 3 — Aggregates và Policies (màu vàng)
Aggregates là các “anchor points” — nơi business rules và data gắn với nhau không thể tách rời. Policies là phản ứng tự động: “Whenever PaymentFailed, notify customer AND release inventory hold.”
Bước 4 — Đánh Dấu Hotspots
Hotspot (sticky đỏ, đặt nghiêng) = “chúng ta không đồng ý về điều này” hoặc “chúng ta không biết điều này”. Không giải quyết ngay — đánh dấu và tiếp tục. Hotspots là output quan trọng nhất: chúng lộ ra unknown unknowns — những nơi team đang lập trình bằng cách đoán mò vì không có “theory of the program” cho vùng đó.
Output của EventStorming
- Bounded Context candidates — nhóm events có cùng actor và axis of change
- Screaming Architecture — folder structure phản ánh business intent (
/order-managementthay vì/controllers) - Danh sách Hotspots — những vùng cần research thêm trước khi code
Ba Phương Pháp, Ba Mục Đích
EventStorming không phải công cụ duy nhất. Đây là so sánh ba phương pháp phổ biến:
| EventStorming | Domain Storytelling | User Story Mapping | |
|---|---|---|---|
| Giải quyết tốt nhất | Tìm service boundaries, lộ “unknown unknowns” | Hiểu workflow và concurrency trong nghiệp vụ | Căn chỉnh những gì cần build tiếp theo |
| Dùng khi | Bắt đầu dự án hoặc khi hệ thống đang trong “Programming Hell” | Design phase — cắt hệ thống thành vertical slices | Cần feedback nhanh, requirements chưa rõ |
| Giới hạn | Event-driven flow khó theo dõi control flow | Dễ trở thành “vẽ đẹp” nếu thiếu cộng tác thực sự | Client thường không đọc tài liệu output |
| Output | Bounded Contexts, Aggregates, Hotspots | Activity Diagrams, Project Glossary | User Stories, Use-Case Diagrams |
| Best for | Khám phá microservice boundaries | Hiểu user flows và luồng nghiệp vụ song song | Quick team alignment về scope |
Không có phương pháp nào tốt hơn tuyệt đối. Nhiều team dùng cả ba theo thứ tự: EventStorming đầu tiên để tìm boundaries, Domain Storytelling để hiểu workflow trong từng context, User Story Mapping để cắt backlog.
Khi Nào Không Nên Tách Bounded Context
Domain Discovery thường tập trung vào việc tìm ranh giới — nhưng biết khi nào không tách cũng quan trọng không kém.
- Code closely related — hai pieces of code chia sẻ thông tin, overlap khái niệm → để chúng cùng nhau
- Shallow modules — nếu interface giữa hai context phức tạp hơn logic bên trong → tách quá nhỏ (classitis)
- Strong dependencies — nếu developer phải liên tục chuyển qua lại giữa hai context → chúng chưa thực sự độc lập
- Premature decoupling — nếu một server là đủ và bạn chưa hiểu axes of change → tách sớm tốn gấp đôi chi phí
Quy tắc ngón tay cái: tách khi bạn thấy operational reason cụ thể (scale khác nhau, deploy frequency khác nhau, team ownership khác nhau) — không phải khi “nghe có vẻ đúng về mặt lý thuyết”.
Kết: Chọn Công Cụ Theo Vấn Đề
Bug regularHours() triệu đô không phải vì team thiếu kỹ năng code. Họ thiếu một shared mental model về domain — cụ thể là: ai owns logic này, trong context nào, và khi nào nó được phép thay đổi. (Mental model không tốt cũng là nguyên nhân phổ biến khiến developer debug chậm — xem thêm: Tại Sao Dev Giỏi Debug 3x Nhanh Hơn?)
Domain Discovery là công việc tạo ra shared mental model đó — trước khi bạn viết một dòng code.
Checklist chọn phương pháp:
- Bạn đang bắt đầu hệ thống mới hoặc cần tìm service boundaries → EventStorming
- Bạn cần hiểu workflow nghiệp vụ và luồng xử lý song song → Domain Storytelling
- Bạn cần align team về scope và prioritize backlog → User Story Mapping
- Bạn chưa chắc boundaries đã đúng → EventStorming + “Independent Deployability” check
Bước tiếp theo thực tế: Tổ chức một buổi EventStorming 2 tiếng với team. Mang sticky notes màu cam. Bắt đầu bằng câu hỏi: “Điều gì xảy ra đầu tiên khi user đặt hàng?” — rồi để team map tiếp. Bạn sẽ thấy hotspot đầu tiên trong vòng 20 phút.
Bài liên quan: Quy Trình Phân Tích Thiết Kế Hệ Thống Phần Mềm: Tại Sao Service Boundary Quyết Định Tất Cả | Tại Sao Dev Giỏi Debug 3x Nhanh Hơn?
Tài liệu tham khảo:


Gửi phản hồi