Mở đầu — vì sao bài này quan trọng
Bạn đã học qua hàng loạt ký hiệu BPMN: Events, Activities, Gateways, Pools, Lanes, Data Objects... Mỗi ký hiệu có một hình dạng và một ý nghĩa riêng. Nhưng có một câu hỏi mà rất nhiều người vẽ BPMN giỏi vẫn không trả lời được một cách chắc chắn: "Khi một process chạy thật, điều gì xảy ra bên trong nó?"
Ví dụ: nếu một Parallel Gateway tách ra ba nhánh, rồi process kết thúc ở một End Event trong khi hai nhánh kia vẫn đang chạy — thì process đã thực sự kết thúc chưa? Nếu một Exclusive Gateway nhận đầu vào từ hai nhánh khác nhau, nó có "chờ" cả hai không? Nếu một sub-process có hai Start Event, cái nào chạy?
Những câu hỏi này không thể trả lời bằng trực giác. Chúng cần một mô hình tư duy (mental model) chính xác — và mô hình đó chính là token semantics. Đây là cách mà chính các BPMN engine như Camunda, Flowable, hay Bizagi Engine hiểu và thực thi sơ đồ của bạn. Nếu bạn nắm được token, bạn sẽ "đọc" được hành vi của một sơ đồ BPMN chỉ bằng mắt, giống như một lập trình viên đọc code và biết nó sẽ chạy ra sao trước khi nhấn nút Run.
Bài này không dạy bạn vẽ thêm ký hiệu mới. Nó dạy bạn làm cho các ký hiệu đã học sống dậy — biến một bức tranh tĩnh thành một quy trình động trong đầu bạn. Đây là ranh giới giữa người "vẽ cho đẹp" và người "thiết kế quy trình chạy được".
Khái niệm cốt lõi
Token là gì?
Hãy tưởng tượng bạn đặt một viên bi (hay một con cờ) lên sơ đồ BPMN. Viên bi này di chuyển dọc theo các Sequence Flow (mũi tên nét liền). Mỗi khi nó đi qua một phần tử — một task, một event, một gateway — phần tử đó được "kích hoạt". Viên bi đó chính là token.
Token là một khái niệm trừu tượng, không có thật trong sơ đồ vẽ ra. Nó là công cụ tư duy mà đặc tả BPMN 2.0 dùng để mô tả hành vi thực thi. Bạn không vẽ token, bạn hình dung nó. Nhưng chính nhờ hình dung đó mà mọi thứ trở nên rõ ràng.
Một điều cực kỳ quan trọng: token chỉ chạy trong một process, dọc theo Sequence Flow. Token không đi qua Message Flow (mũi tên nét đứt nối giữa hai Pool). Khi process A gửi message cho process B, token của A không "nhảy" sang B — nó dừng lại chờ, còn B có token riêng của nó. Đây là lý do vì sao Message Flow và Sequence Flow là hai thứ hoàn toàn khác nhau về mặt thực thi.
Bốn quy tắc nền tảng
Toàn bộ token semantics có thể quy về một vài quy tắc đơn giản. Nắm chắc bốn điều sau là bạn đã đi được 80% chặng đường:
Quy tắc 1 — Khởi tạo. Khi một process instance bắt đầu, một token được tạo ra tại Start Event. Mỗi lần Start Event được kích hoạt là một token mới, tương ứng một instance mới (ví dụ: mỗi đơn hàng mới = một token mới chạy trên cùng một sơ đồ).
Quy tắc 2 — Di chuyển. Token đi từ phần tử này sang phần tử kế tiếp dọc theo Sequence Flow. Một Activity chỉ thực thi khi có token đến; thực thi xong, nó chuyển token đi tiếp.
Quy tắc 3 — Nhân và hợp tại Gateway. Đây là phần thú vị nhất. Gateway không "làm việc" gì cả; chúng chỉ điều khiển số lượng và hướng đi của token:
- Parallel Gateway (AND) — split: nhận 1 token, tạo ra N token, mỗi nhánh đầu ra một token. Tất cả các nhánh cùng chạy.
- Parallel Gateway (AND) — join: chờ đủ token từ tất cả các nhánh đầu vào, rồi gộp thành 1 token đi tiếp. Đây gọi là synchronization (đồng bộ hóa).
- Exclusive Gateway (XOR) — split: nhận 1 token, chỉ chuyển nó sang đúng một nhánh đầu ra (theo điều kiện). Số token không đổi.
- Exclusive Gateway (XOR) — join: mỗi token đến đều đi tiếp ngay, không chờ nhánh nào cả.
Vì sao "process kết thúc = hết token", không phải "chạm End Event"
Đặc tả BPMN dùng cái gọi là Normal End Event với ngữ nghĩa "consume token". Khác với một số notation cũ, một End Event trong BPMN không dập tắt cả process. Nó chỉ dập tắt token đang đến nó.
Hệ quả: nếu bạn có một Parallel split tạo ra 3 token, và một trong ba nhánh chạm End Event sớm, thì process vẫn còn 2 token đang chạy — instance chưa kết thúc. Process chỉ "chết" khi cả 3 token đều đã bị tiêu thụ. Quy tắc này gọi là process completion semantics, và nó là nguồn gốc của vô số hiểu lầm trong thực tế.
Một ngoại lệ cần phân biệt: Terminate End Event (End Event có hình tròn đậm bên trong) thì khác — nó xóa sạch mọi token còn lại trong process ngay lập tức, kết thúc cưỡng bức. Đây là công cụ duy nhất để "dừng tất cả các nhánh song song giữa chừng".
Token và sub-process
Khi token đi vào một Sub-process (call hoặc embedded), nó "bước vào" một thế giới con. Bên trong sub-process, một token mới được sinh ra tại Start Event nội bộ và chạy theo cùng các quy tắc trên. Token cha ở process ngoài bị "treo" (waiting) tại biên sub-process cho đến khi mọi token con bên trong đã bị tiêu thụ. Lúc đó, token cha mới đi tiếp. Đây là cách BPMN bọc một quy trình con thành một đơn vị nguyên tử nhìn từ bên ngoài.
Tình huống thực tế
Ví dụ 1 — Đơn hàng tại Tiki: parallel split khiến đơn "treo" mãi không đóng
Một nhóm vận hành tại một sàn thương mại điện tử giả định kiểu Tiki vẽ quy trình xử lý đơn hàng như sau. Sau khi khách đặt hàng, một Parallel Gateway tách ra hai nhánh chạy song song: nhánh A là "Soạn hàng và đóng gói", nhánh B là "Xử lý thanh toán qua cổng VNPay". Nhóm muốn hai việc này chạy đồng thời để rút ngắn thời gian.
Nhưng họ vẽ sai phần kết thúc: mỗi nhánh tự đi tới một End Event riêng, không có Parallel join để gộp lại. Trên giấy nhìn rất gọn. Nhưng khi đưa vào Camunda chạy thật, đội vận hành phát hiện một hiện tượng kỳ lạ: nhiều đơn hàng cứ "treo" ở trạng thái active dù khách đã nhận hàng từ lâu.
Diễn giải bằng token: parallel split sinh ra 2 token. Nhánh thanh toán (B) chạy nhanh, token B chạm End Event và bị tiêu thụ. Nhưng nhánh soạn hàng (A) — nếu kho hết hàng, token A bị kẹt ở task "Soạn hàng" chờ nhập kho. Vì process chỉ đóng khi hết sạch token, mà token A vẫn còn, nên cả instance không bao giờ tự đóng. Dashboard hiển thị hàng nghìn đơn "đang xử lý" giả tạo.
Bài học: mỗi lần bạn split bằng Parallel Gateway, bạn nợ hệ thống một Parallel join để thu các token về. Hai End Event tách rời không gây lỗi cú pháp, nhưng tạo ra hành vi token mà bạn không lường trước. Vẽ đúng: hai nhánh phải gặp nhau tại một Parallel join, rồi mới đi tới một End Event chung.
Ví dụ 2 — Phê duyệt hồ sơ vay tại một ngân hàng: nhầm XOR join với AND join
Tại bộ phận thẩm định của một ngân hàng giả định ở TP.HCM, quy trình phê duyệt khoản vay có một Exclusive Gateway phân nhánh theo hạn mức: hồ sơ dưới 500 triệu đi nhánh "Trưởng phòng duyệt", từ 500 triệu trở lên đi nhánh "Giám đốc duyệt". Sau khi duyệt xong, cả hai nhánh quay về một điểm chung để "Giải ngân".
Một nhân viên BA mới vẽ điểm gặp nhau đó bằng một Parallel Gateway (AND join) vì nghĩ "gộp lại thì dùng dấu cộng". Kết quả: process treo ở 100% số hồ sơ. Vì sao?
Diễn giải bằng token: Exclusive split chỉ tạo ra một token — nó đi vào một trong hai nhánh, không phải cả hai. Nhưng AND join lại chờ đủ token từ tất cả các nhánh đầu vào. Nó nhận được 1 token từ nhánh "Trưởng phòng", rồi đứng chờ mãi token thứ hai từ nhánh "Giám đốc" — token mà sẽ không bao giờ tồn tại vì hồ sơ này chỉ đi một nhánh. Token bị kẹt vĩnh viễn ở AND join. Đây là một deadlock kinh điển.
Sửa đúng: điểm gặp nhau sau một XOR split phải là một XOR join (mỗi token đến là đi tiếp ngay, không chờ). Quy tắc cặp đôi để nhớ: XOR split đi với XOR join, AND split đi với AND join. Trộn lẫn là nguồn gốc của deadlock hoặc token trùng lặp.
Bài học: Gateway không có ý nghĩa nghiệp vụ — chúng chỉ có ý nghĩa về token. Hỏi đúng câu hỏi: "Tại điểm này có bao nhiêu token thực sự đến?" sẽ chọn đúng loại gateway ngay.
Ví dụ 3 — Quy trình onboarding nhân sự tại FPT Software: Terminate cứu một quy trình bế tắc
Một đội thiết kế quy trình tại một công ty phần mềm lớn kiểu FPT Software xây quy trình onboarding nhân viên mới. Sau khi ký hợp đồng, một Parallel split tạo ra ba nhánh song song: (A) cấp tài khoản IT, (B) chuẩn bị bàn ghế chỗ ngồi, (C) đăng ký khóa đào tạo hội nhập. Cả ba chạy đồng thời, rồi gộp tại Parallel join để "Báo cáo hoàn tất onboarding".
Vấn đề phát sinh: thỉnh thoảng ứng viên rút lui giữa chừng (nhận offer chỗ khác). Khi đó nghiệp vụ cần hủy toàn bộ quá trình onboarding ngay, kể cả khi nhánh IT đã cấp xong tài khoản. Nhóm ban đầu thử dùng một End Event thường ở nhánh "Phát hiện ứng viên rút lui", nhưng vô ích — token nhánh đó biến mất, còn hai token A và B vẫn tiếp tục chạy, IT vẫn cấp tài khoản cho một người sẽ không bao giờ đến làm.
Giải pháp: thay End Event thường bằng Terminate End Event. Khi token nhánh "rút lui" chạm Terminate, mọi token còn lại trong instance bị xóa ngay lập tức — nhánh A, B, C đều dừng. Process kết thúc sạch sẽ.
Bài học: Hiểu sự khác biệt giữa "consume một token" (Normal End) và "xóa mọi token" (Terminate End) là điều kiện để thiết kế các quy trình có khả năng "hủy giữa chừng". Nhiều quy trình thực tế — hủy đơn, hủy hồ sơ, ngắt khẩn cấp — đều phụ thuộc vào ngữ nghĩa này.
Hướng dẫn từng bước
Đây là quy trình "chạy token bằng tay" (token walkthrough) mà bạn nên áp dụng mỗi khi review một sơ đồ BPMN bất kỳ. Hãy lấy một cây bút và làm theo:
Bước 1 — Đặt token đầu tiên. Vẽ một chấm tròn (đại diện token) lên Start Event. Nếu sơ đồ có nhiều Start Event độc lập, bạn đang xét một kịch bản — chọn một và đặt token ở đó.
Bước 2 — Di chuyển token theo Sequence Flow. Lần theo mũi tên nét liền. Mỗi khi token đi qua một Activity, ghi chú "task này thực thi". Token đứng yên (chờ) tại các task cần thời gian hoặc tại các Catch Event đang chờ tín hiệu.
Bước 3 — Xử lý mỗi Gateway theo loại. Khi token chạm một gateway, dừng lại và hỏi:
- Là gateway split (1 vào, nhiều ra) hay join (nhiều vào, 1 ra)?
- Nếu là AND: split thì nhân token ra mọi nhánh; join thì chờ đủ token mọi nhánh.
- Nếu là XOR: split thì chuyển token sang đúng một nhánh; join thì cho qua ngay không chờ.
- Nếu là Inclusive (OR): split thì tạo token cho mỗi nhánh có điều kiện đúng; join thì chờ đúng số token tương ứng số nhánh đã được kích hoạt.
Bước 5 — Kiểm tra điều kiện kết thúc. Lần theo cho đến khi tổng số token = 0. Nếu bạn thấy một token bị kẹt mãi (không có nhánh nào dẫn đi, hoặc đang chờ token mà sẽ không bao giờ đến) — bạn vừa tìm ra một deadlock. Nếu một End Event đóng instance trong khi vẫn còn token khác — bạn vừa tìm ra một "đơn treo" tiềm năng.
Bước 6 — Thử các kịch bản khác nhau. Quay lại Bước 1 với một đường rẽ XOR khác, một điều kiện Inclusive khác. Một sơ đồ tốt phải đúng với mọi đường đi của token, không chỉ đường "happy path".
Lỗi thường gặp & mẹo
Lỗi 1 — Tin rằng chạm End Event là process kết thúc. Như đã phân tích, End Event thường chỉ tiêu thụ token đang đến nó. Process kết thúc khi hết token. Hãy luôn đếm token, đừng nhìn End Event.
Lỗi 2 — Trộn loại split và join. AND split + XOR join tạo ra token trùng (mỗi nhánh chạy join một lần → các bước sau bị thực thi nhiều lần). XOR split + AND join tạo deadlock. Mẹo: dùng đúng cặp, và nếu phải trộn vì lý do nghiệp vụ, hãy dùng Inclusive Gateway (OR) một cách có chủ đích.
Lỗi 3 — Tưởng token đi qua Message Flow. Token không bao giờ băng qua biên Pool. Giữa hai Pool chỉ có Message Flow, và mỗi Pool có token riêng. Nếu bạn thấy mình "kéo" một token từ Pool khách hàng sang Pool công ty, bạn đang hiểu sai mô hình.
Lỗi 4 — Quên rằng token cha chờ token con ở sub-process. Sub-process là một "hộp đen" với bên ngoài: token cha chỉ đi tiếp khi mọi token con bên trong đã tiêu thụ hết. Nếu bên trong sub-process có một nhánh treo, token cha cũng treo theo.
Mẹo 1 — Dùng màu để đếm. Khi review sơ đồ phức tạp, dùng bút màu khác nhau cho mỗi token sau một parallel split. Bạn sẽ thấy ngay nhánh nào "lạc" và không bao giờ về tới join.
Mẹo 2 — Đối chiếu với engine thật. Camunda Modeler có chế độ token simulation cho phép bạn nhấn nút và xem token chạy bằng hoạt hình. Hãy dùng nó để kiểm chứng trực giác của bạn — đặc biệt với các nhánh song song và inclusive.
Mẹo 3 — Mỗi split là một lời hứa. Tự đặt luật: "đã split thì phải có join tương ứng cùng loại, trừ khi tôi cố ý dùng Terminate". Luật này một mình đã loại bỏ phần lớn lỗi treo và deadlock.
Bài tập thực hành
Bài 1 — Walkthrough cơ bản. Vẽ một process: Start → AND split → (Task A | Task B) → AND join → End. Hãy viết ra trình tự token: có bao nhiêu token sau split? AND join chờ gì? Khi nào instance kết thúc?
Bài 2 — Phát hiện deadlock. Cho sơ đồ: Start → XOR split (theo điều kiện "VIP / thường") → (Task VIP | Task thường) → AND join → End. Hãy giải thích bằng token vì sao sơ đồ này luôn treo, và sửa lại cho đúng.
Bài 3 — Đơn treo. Vẽ quy trình giao hàng có Parallel split thành hai nhánh "giao hàng" và "thu tiền COD", mỗi nhánh đi tới một End Event riêng. Mô phỏng token cho trường hợp nhánh "thu tiền" bị kẹt (khách chưa trả). Instance có đóng không? Vẽ lại phiên bản đúng.
Bài 4 — Terminate. Lấy lại quy trình onboarding ba nhánh song song trong Ví dụ 3. Thêm một nhánh "ứng viên rút lui" và thiết kế sao cho khi nhánh này kích hoạt, toàn bộ instance dừng ngay. Chỉ ra chính xác chỗ đặt Terminate End Event và giải thích bằng token.
Bài 5 — Tự kiểm chứng. Mở Camunda Modeler (hoặc bất kỳ công cụ có token simulation), dựng lại Bài 1 và Bài 2, chạy mô phỏng và đối chiếu với phần bạn đã viết tay. Ghi lại điểm khác biệt (nếu có) giữa trực giác của bạn và hành vi engine.
Tóm tắt
Token semantics là mô hình tư duy để "chạy" một sơ đồ BPMN trong đầu. Hãy ghi nhớ những điểm cốt lõi:
- Token là một viên bi tưởng tượng chạy dọc Sequence Flow; nó kích hoạt mỗi phần tử nó đi qua, và không bao giờ đi qua Message Flow hay băng biên Pool.
- Process bắt đầu với một token tại Start Event; mỗi instance là một token (hoặc tập token) độc lập.
- Gateway chỉ điều khiển số lượng và hướng token, không làm việc nghiệp vụ: AND nhân/đồng bộ tất cả nhánh, XOR chọn/cho-qua một nhánh, OR theo điều kiện.
- Process kết thúc khi không còn token nào — không phải khi token đầu tiên chạm End Event. Normal End "tiêu thụ một token"; Terminate End "xóa mọi token".
- Cặp split–join phải cùng loại; trộn lẫn gây deadlock hoặc thực thi trùng — hai lỗi tốn kém nhất khi đưa quy trình vào engine thật.