Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 19 — Bounded Context & Domain-Driven Design

Technical BA Masterclass Bài 19/60

Mở đầu — vì sao bài này quan trọng

Hãy hình dung bạn đang làm BA cho một dự án bảo hiểm. Trong cuộc họp với phòng kinh doanh, ai cũng nói về "Hợp đồng" (Policy). Bạn ghi chú cẩn thận, vẽ data model, định nghĩa trường dữ liệu. Một tuần sau, bạn họp với phòng bồi thường (claims) — họ cũng nói về "Hợp đồng", nhưng khi đào sâu bạn phát hiện cái "Hợp đồng" của họ chỉ quan tâm tới quyền lợi, hạn mức và trạng thái còn hiệu lực hay không, gần như không quan tâm tới quy trình bán hàng, hoa hồng đại lý hay lịch sử báo giá. Cùng một từ, hai ý nghĩa khác nhau.

Nếu bạn gộp tất cả vào một model "Policy" khổng lồ để "dùng chung cho cả công ty", bạn vừa gieo một quả bom hẹn giờ. Model đó sẽ phình to, mỗi lần phòng kinh doanh đổi quy tắc thì phòng bồi thường cũng bị ảnh hưởng, hai đội dev giẫm chân nhau, và spec của bạn trở thành nơi mọi tranh cãi ngữ nghĩa hội tụ.

Domain-Driven Design (DDD) — và đặc biệt là khái niệm Bounded Context — chính là công cụ để bạn xử lý đúng tình huống này. Đây không phải kiến thức "của dev". Với Technical BA, Bounded Context là cách bạn vẽ ranh giới nghiệp vụ một cách có kỷ luật, để mỗi vùng có ngôn ngữ riêng, model riêng, và spec riêng — tránh được những hiểu lầm tốn hàng tháng trời rework. Bài này sẽ giúp bạn nắm vững khái niệm, biết cách nhận diện ranh giới, và biết cách thể hiện chúng trong tài liệu của mình.

Khái niệm cốt lõi

Domain-Driven Design là gì, và BA liên quan thế nào?

DDD là một cách tiếp cận thiết kế phần mềm do Eric Evans giới thiệu năm 2003, đặt nghiệp vụ (domain) làm trung tâm thay vì để công nghệ dẫn dắt. Tư tưởng cốt lõi rất gần với công việc của BA: muốn xây hệ thống đúng, đội phát triển phải hiểu sâu nghiệp vụ, và cách hiểu đó phải được phản ánh trực tiếp vào thiết kế và code.

DDD có nhiều khái niệm (Entity, Value Object, Aggregate, Repository...), nhưng với Technical BA, ba thứ quan trọng nhất là: Ubiquitous Language, Bounded Context, và Context Map. Đây là những thứ bạn trực tiếp tạo ra và bảo vệ trong tài liệu.

Ubiquitous Language — ngôn ngữ chung

Ubiquitous Language là bộ thuật ngữ thống nhất mà cả nghiệp vụ và kỹ thuật cùng dùng, không dịch qua dịch lại. Nếu nghiệp vụ gọi là "đơn hàng chờ duyệt", thì trong code, trong database, trong API và trong spec đều phải xuất hiện khái niệm đó (ví dụ PendingOrder), chứ không phải dev tự đặt tên OrderStatus = 3 rồi không ai hiểu.

Vai trò của BA ở đây cực kỳ rõ: bạn là người chắp bút bảng thuật ngữ (glossary), đảm bảo mỗi từ có đúng một định nghĩa trong phạm vi của nó. Lưu ý cụm "trong phạm vi của nó" — vì cùng một từ có thể có nghĩa khác nhau ở vùng khác, và đó chính là lý do ta cần Bounded Context.

Bounded Context — ranh giới của ngôn ngữ

Bounded Context là vùng mà một mô hình ngôn ngữ (ubiquitous language) áp dụng nhất quán. Bên trong một Bounded Context, mỗi thuật ngữ có đúng một nghĩa. Khi bước qua ranh giới sang context khác, cùng một từ có thể mang nghĩa khác — và điều đó hoàn toàn bình thường, thậm chí đáng mong muốn.

Ví dụ kinh điển: từ "Customer" (Khách hàng).

  • Trong Sales Context (bán hàng): Customer là một lead/cơ hội, có thông tin liên hệ, lịch sử tương tác, giai đoạn trong phễu bán hàng, đại diện kinh doanh phụ trách. Customer ở đây có thể còn chưa mua gì.
  • Trong Billing Context (thanh toán): Customer là một tài khoản chịu trách nhiệm tài chính, có mã số thuế, phương thức thanh toán, hạn mức công nợ, lịch sử hóa đơn.
  • Trong Support Context (hỗ trợ): Customer là một người cần được hỗ trợ, gắn với các ticket, mức độ ưu tiên, hợp đồng SLA.
Ba "Customer" này không phải là một bảng dữ liệu duy nhất. Chúng là ba model khác nhau, được liên kết với nhau qua một định danh chung (ví dụ customer_id), nhưng mỗi context chỉ giữ những thuộc tính và hành vi mà quan tâm. Ép ba thứ này vào một "God Object" duy nhất là sai lầm phổ biến nhất mà DDD muốn bạn tránh.

Context Map — bản đồ quan hệ giữa các context

Khi bạn đã chia hệ thống thành nhiều Bounded Context, câu hỏi tiếp theo là: chúng nói chuyện với nhau như thế nào? Context Map là sơ đồ mô tả mối quan hệ và cách tích hợp giữa các context. Một số kiểu quan hệ BA hay gặp:

  • Customer–Supplier: context A (downstream) phụ thuộc vào dữ liệu từ context B (upstream). B cần lắng nghe nhu cầu của A.
  • Conformist: downstream buộc phải dùng đúng model của upstream, không có quyền thương lượng (thường gặp khi tích hợp hệ thống của bên thứ ba).
  • Anti-Corruption Layer (ACL): downstream xây một lớp dịch thuật để "phiên dịch" model của upstream sang model của mình, tránh để model bẩn của bên ngoài làm hỏng model sạch của mình. Đây là pattern cực kỳ quan trọng khi tích hợp với hệ thống legacy.
  • Shared Kernel: hai context cùng chia sẻ một phần model chung nhỏ, cần phối hợp chặt khi thay đổi.
  • Open Host Service / Published Language: một context cung cấp API/định dạng công khai để nhiều context khác cùng dùng.
Với Technical BA, Context Map chính là cầu nối giữa bức tranh nghiệp vụ và bức tranh tích hợp kỹ thuật. Nó cho bạn biết thay đổi ở chỗ này sẽ lan tới đâu.

Bounded Context khác gì với Microservice?

Đây là điểm hay gây nhầm. Một Bounded Context có thể tương ứng với một microservice, nhưng không nhất thiết. Bounded Context là khái niệm về ranh giới ngữ nghĩa/logic của nghiệp vụ; microservice là một lựa chọn triển khai kỹ thuật. Bạn hoàn toàn có thể có một monolith chứa nhiều Bounded Context được tách rõ ràng bằng module/namespace. Ngược lại, tách microservice mà không tôn trọng ranh giới context thì chỉ tạo ra "distributed monolith" — phân tán nhưng vẫn dính chặt vào nhau.

Tình huống thực tế

Ví dụ 1: Sàn TMĐT giả định "ChợViệt" và từ "Sản phẩm"

ChợViệt là một sàn thương mại điện tử nội địa với khoảng 2 triệu SKU. Ban đầu đội kỹ thuật có một bảng products duy nhất với hơn 80 cột, dùng chung cho mọi nơi. Hệ quả: mỗi lần đội kho (warehouse) cần thêm trường về kích thước đóng gói, đội marketing lại sợ ảnh hưởng tới trang chi tiết sản phẩm; mỗi lần đội tìm kiếm muốn tối ưu, họ vướng vào hàng chục trường không liên quan.

BA ngồi lại và nhận ra "Sản phẩm" thực ra là ba khái niệm khác nhau:

  • Trong Catalog Context: sản phẩm là thông tin hiển thị — tên, mô tả, hình ảnh, thuộc tính marketing, đánh giá. Mục tiêu là bán được hàng.
  • Trong Inventory Context: sản phẩm là một mặt hàng tồn kho — SKU, số lượng tại từng kho, kích thước đóng gói, trạng thái còn/hết hàng. Mục tiêu là đảm bảo giao được hàng.
  • Trong Pricing Context: sản phẩm là một đối tượng được định giá — giá gốc, khuyến mãi, quy tắc giá theo thời điểm, biên lợi nhuận. Mục tiêu là tối ưu doanh thu.
Sau khi tách thành ba Bounded Context, ba đội làm việc song song mà không giẫm chân nhau. Khi đội kho đổi cách tính kích thước đóng gói, Catalog và Pricing hoàn toàn không bị ảnh hưởng. Định danh chung là product_id, và mỗi context tự bổ sung dữ liệu của mình.

Bài học rút ra: dấu hiệu rõ nhất để tách context là khi cùng một thực thể có nhiều mục tiêu nghiệp vụ khác nhaucác nhóm khác nhau thay đổi vì lý do khác nhau. Hỏi câu "ai thay đổi cái này, và vì sao?" — nếu câu trả lời ra nhiều nhóm với lý do tách biệt, đó là tín hiệu của nhiều context.

Ví dụ 2: Ví điện tử "MoMo-like" và từ "Giao dịch"

Một ví điện tử nội địa (gọi là VíViệt) gặp vấn đề khi định nghĩa "Giao dịch" (Transaction). Đội thanh toán, đội đối soát và đội chống gian lận đều dùng từ này nhưng nghĩa lệch nhau, dẫn tới spec mâu thuẫn.

BA phân tích và xác định:

  • Payment Context: "Transaction" là một lệnh chuyển tiền đang thực thi, quan tâm tới trạng thái (pending, success, failed), nguồn tiền, đích đến, idempotency key để tránh trừ tiền hai lần.
  • Reconciliation Context (đối soát): "Transaction" là một bản ghi cần khớp với sao kê ngân hàng/đối tác, quan tâm tới thời điểm ghi sổ, mã tham chiếu bên thứ ba, trạng thái đã/chưa đối soát.
  • Fraud Context (chống gian lận): "Transaction" là một sự kiện hành vi để chấm điểm rủi ro, quan tâm tới thiết bị, vị trí, tần suất, mối liên hệ với các giao dịch khác trong 24 giờ.
Họ thiết kế Payment Context là upstream phát ra sự kiện TransactionCompleted; Reconciliation và Fraud là downstream lắng nghe sự kiện đó qua một message queue. Quan trọng: Fraud Context xây một Anti-Corruption Layer để biến sự kiện thô từ Payment thành model rủi ro riêng của nó, nên khi Payment đổi cấu trúc nội bộ, Fraud không vỡ.

Kết quả: trong một đợt nâng cấp cổng thanh toán, đội Payment thay đổi mạnh model nội bộ nhưng nhờ ranh giới rõ và lớp ACL, hai đội còn lại gần như không phải sửa gì. Trước đó, cùng một thay đổi tương tự đã từng khiến cả ba đội rework mất gần 3 tuần.

Bài học rút ra: ranh giới context không chỉ giúp tách model, mà còn xác định hướng phụ thuộcđiểm tích hợp. BA cần chỉ rõ đâu là upstream, đâu là downstream, và chỗ nào cần ACL để cô lập rủi ro.

Ví dụ 3: Ngân hàng số và sai lầm "một model dùng chung"

Một ngân hàng số tại Đông Nam Á triển khai dự án onboarding khách hàng mới. BA đầu tiên định nghĩa một thực thể "Account" (Tài khoản) dùng chung cho toàn bộ hệ thống, kỳ vọng "đỡ trùng lặp dữ liệu". Sau 4 tháng, dự án sa lầy: thực thể Account có hơn 120 thuộc tính, vì nó phải gánh đồng thời nghĩa "tài khoản đăng nhập" (identity), "tài khoản tiền gửi" (deposit account), và "hồ sơ khách hàng" (KYC profile). Mỗi thay đổi nhỏ kéo theo họp liên phòng ban.

Một Technical BA mới vào được giao "gỡ rối". Anh tách thành ba context: Identity & Access (đăng nhập, xác thực, vai trò), Core Banking (tài khoản tiền gửi, số dư, sao kê), và KYC/Onboarding (định danh, giấy tờ, mức độ rủi ro tuân thủ). Mỗi context có glossary riêng, và "Account" chỉ tồn tại trong Core Banking; ở Identity nó là "User", ở KYC nó là "Customer Profile".

Bài học rút ra: "dùng chung một model để tránh trùng lặp" nghe có vẻ tiết kiệm nhưng thường phản tác dụng. Một chút trùng lặp dữ liệu được kiểm soát giữa các context (qua định danh chung) rẻ hơn nhiều so với việc tạo ra một model dính chặt mà không ai dám đụng vào.

Hướng dẫn từng bước

Đây là quy trình thực dụng để một Technical BA xác định và mô tả Bounded Context.

Bước 1 — Thu thập ngôn ngữ nghiệp vụ. Phỏng vấn từng nhóm người dùng/phòng ban. Ghi lại các danh từ (thực thể) và động từ (hành vi) họ dùng. Chú ý đặc biệt tới những từ xuất hiện ở nhiều nhóm.

Bước 2 — Phát hiện xung đột ngữ nghĩa. Lập bảng: với mỗi thuật ngữ lặp lại (Customer, Product, Order, Transaction...), ghi xem mỗi nhóm hiểu nó là gì, cần thuộc tính nào, hành vi nào. Nếu định nghĩa lệch nhau đáng kể, bạn đã tìm thấy một ranh giới context tiềm năng.

Bước 3 — Nhóm theo năng lực nghiệp vụ (business capability). Gom các hành vi và thực thể quanh từng mục tiêu nghiệp vụ độc lập: "bán hàng", "quản lý kho", "thu tiền", "hỗ trợ". Mỗi nhóm năng lực thường là một ứng viên Bounded Context.

Bước 4 — Xác định Ubiquitous Language cho từng context. Viết glossary riêng cho mỗi context. Trong glossary, ghi rõ: trong context này, "Customer" nghĩa là gì, gồm thuộc tính nào. Đừng cố ép một định nghĩa cho mọi nơi.

Bước 5 — Vẽ Context Map. Với mỗi cặp context có trao đổi dữ liệu, xác định: ai upstream/downstream, kiểu quan hệ (Customer-Supplier, Conformist, ACL...), dữ liệu trao đổi là gì, đồng bộ hay bất đồng bộ. Đây là phần kết nối nghiệp vụ với thiết kế tích hợp.

Bước 6 — Đưa vào tài liệu spec. Mỗi context nên có một mục riêng trong tài liệu: glossary, các quy tắc nghiệp vụ, model dữ liệu, và các điểm tích hợp với context khác. Khi viết yêu cầu, luôn ghi rõ "yêu cầu này thuộc context nào".

Lỗi thường gặp & mẹo

Lỗi 1 — Tạo "God Model" dùng chung. Như cả ba ví dụ trên cho thấy, gộp mọi nghĩa vào một thực thể là cái bẫy phổ biến nhất. Mẹo: mỗi khi muốn thêm thuộc tính vào một thực thể "vì một phòng ban khác cần", hãy dừng lại và hỏi liệu đó có phải là một context khác không.

Lỗi 2 — Tách context theo cấu trúc kỹ thuật thay vì nghiệp vụ. Đừng tách kiểu "Frontend Context", "Database Context". Context phải bám vào năng lực nghiệp vụ, không phải tầng kỹ thuật.

Lỗi 3 — Vẽ ranh giới quá nhỏ, quá nhiều. Tách quá mảnh tạo ra hàng chục context tí hon, chi phí tích hợp bùng nổ. Mẹo: một context nên đủ lớn để một đội hiểu trọn vẹn nó, nhưng đủ nhỏ để không có hai định nghĩa mâu thuẫn cho cùng một từ.

Lỗi 4 — Quên Anti-Corruption Layer khi tích hợp legacy. Khi nối với hệ thống cũ hoặc bên thứ ba, nếu để model bẩn của họ rò rỉ thẳng vào hệ thống mới, bạn sẽ "nhiễm bẩn" model sạch của mình. Luôn cân nhắc một ACL để phiên dịch.

Lỗi 5 — Glossary không có phạm vi. Một bảng thuật ngữ toàn công ty với một định nghĩa duy nhất cho "Customer" sẽ luôn sai ở đâu đó. Mẹo: mỗi định nghĩa phải ghi kèm "trong context nào".

Mẹo bổ sung — Dùng kỹ thuật Event Storming. Khi cần khám phá ranh giới nhanh, tổ chức một buổi Event Storming: dán giấy nhớ mô tả các sự kiện nghiệp vụ (dạng "Đơn hàng đã được đặt", "Tiền đã được trừ") lên tường theo dòng thời gian. Những cụm sự kiện co cụm tự nhiên thường lộ ra ranh giới Bounded Context.

Bài tập thực hành

Hãy chọn một hệ thống bạn quen thuộc (hoặc dùng tình huống "ứng dụng đặt đồ ăn" như GrabFood/ShopeeFood) và làm các bước sau:

  • Liệt kê thuật ngữ. Viết ra 8–10 thuật ngữ nghiệp vụ chính (ví dụ: Đơn hàng, Tài xế, Nhà hàng, Khách hàng, Món ăn, Khuyến mãi...).
  • Tìm từ đa nghĩa. Chọn ít nhất 2 thuật ngữ xuất hiện ở nhiều nhóm (gợi ý: "Đơn hàng" với đội nhà hàng, đội giao vận, và đội thanh toán có nghĩa khác nhau). Mô tả mỗi nhóm hiểu nó thế nào.
  • Đề xuất Bounded Context. Đề xuất 3–4 Bounded Context cho hệ thống, đặt tên theo năng lực nghiệp vụ. Với mỗi context, ghi 3–4 thực thể chính của nó.
  • Vẽ Context Map đơn giản. Chọn 2 cặp context có trao đổi dữ liệu, ghi rõ ai upstream/downstream, dữ liệu trao đổi, và có cần ACL không.
  • Viết một mục glossary. Viết định nghĩa cho từ "Đơn hàng" trong đúng một context cụ thể, kèm các thuộc tính context đó quan tâm.
Sản phẩm cuối: một trang tài liệu gồm danh sách context, glossary có phạm vi, và sơ đồ Context Map. Đây chính là dạng đầu ra mà một Technical BA thực thụ tạo ra ở giai đoạn đầu của một dự án lớn.

Tóm tắt

Bounded Context là một trong những công cụ tư duy mạnh nhất mà DDD trao cho Technical BA. Ý tưởng cốt lõi rất đơn giản nhưng sâu sắc: một thuật ngữ chỉ nên có một nghĩa trong phạm vi của nó, và phạm vi đó cần được vẽ ranh giới rõ ràng. Cùng từ "Customer", "Product" hay "Transaction" có thể — và nên — mang nghĩa khác nhau ở các vùng nghiệp vụ khác nhau.

Ba thứ bạn cần nắm chắc: Ubiquitous Language (ngôn ngữ chung, thống nhất giữa nghiệp vụ và kỹ thuật), Bounded Context (ranh giới mà ngôn ngữ đó áp dụng nhất quán), và Context Map (bản đồ quan hệ và tích hợp giữa các context, kèm các pattern như ACL). Tránh cái bẫy "God Model dùng chung", tách context theo năng lực nghiệp vụ chứ không theo tầng kỹ thuật, và luôn ghi rõ định nghĩa thuật ngữ thuộc context nào. Khi làm tốt việc này, spec của bạn sẽ rõ ràng, các đội làm việc song song không giẫm chân nhau, và hệ thống chịu được thay đổi mà không vỡ dây chuyền — đó chính là dấu ấn của một Technical BA giỏi.