Large wall chart with eventstorming order lifecycle diagram using colored sticky notes

Khám Phá Domain Trong Phần Mềm: EventStorming, Domain Storytelling, User Story Mapping. Phương Pháp Nào Cho Hệ Thống Của Bạn?

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:

  1. Điều gì xảy ra trong hệ thống này? (Events — những sự kiện đã xảy ra, immutable facts)
  2. Ai trigger điều đó? (Actors + Commands — intent, chưa được validate)
  3. Ở đâ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-management thay 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:

EventStormingDomain StorytellingUser Story Mapping
Giải quyết tốt nhấtTì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 khiBắt đầu dự án hoặc khi hệ thống đang trong “Programming Hell”Design phase — cắt hệ thống thành vertical slicesCần feedback nhanh, requirements chưa rõ
Giới hạnEvent-driven flow khó theo dõi control flowDễ 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
OutputBounded Contexts, Aggregates, HotspotsActivity Diagrams, Project GlossaryUser Stories, Use-Case Diagrams
Best forKhám phá microservice boundariesHiểu user flows và luồng nghiệp vụ song songQuick 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:


Comments

Gửi phản hồi

Khám phá thêm từ Hiểu Code

Đăng ký ngay để tiếp tục đọc và truy cập kho lưu trữ đầy đủ.

Tiếp tục đọc