Mở đầu — vì sao bài này quan trọng
Nếu bạn từng thiết kế một luồng đặt hàng, một quy trình duyệt hồ sơ, hay một màn hình "chờ xác nhận" trong Figma, thì thực ra bạn đã từng chạm vào state machine mà không gọi tên nó. Nhưng ở vai trò Designer, bạn thường vẽ các màn hình — còn ở vai trò BA, bạn phải mô tả các trạng thái và những gì được phép chuyển đổi giữa chúng. Đây là một trong những khác biệt tư duy khó nhằn nhất khi chuyển từ Design sang Business Analysis.
Vì sao khó? Vì Designer quen tư duy theo "happy path" — luồng đẹp nhất, người dùng làm đúng, mọi thứ trôi chảy. Còn dev và tester thì sống trong thế giới của "cái gì xảy ra nếu...". Nếu đơn hàng đang giao mà khách hủy thì sao? Nếu hồ sơ KYC bị từ chối rồi khách nộp lại thì trạng thái quay về đâu? Nếu thanh toán timeout giữa chừng? Những câu hỏi này chính là ngôn ngữ của state machine. Một BA nắm vững tư duy trạng thái sẽ viết requirement chặt chẽ hơn hẳn, ít bug "lọt lưới" hơn, và được dev tin tưởng hơn — vì bạn đã nghĩ thay họ những case xấu.
Bài này sẽ giúp bạn dịch trực giác thiết kế luồng của mình thành một công cụ nghiệp vụ chuẩn xác: state machine — mô hình mô tả một đối tượng nghiệp vụ có thể ở những trạng thái nào và di chuyển giữa chúng ra sao.
Khái niệm cốt lõi
State machine là gì
Một state machine (máy trạng thái) mô tả rằng: mỗi đối tượng nghiệp vụ tại một thời điểm chỉ ở đúng một trạng thái trong một tập hợp trạng thái hữu hạn, và chỉ có thể di chuyển sang trạng thái khác thông qua những transition (chuyển đổi) được định nghĩa rõ ràng.
Bốn thành phần bạn cần nắm:
- State (trạng thái): tình huống ổn định mà đối tượng đang ở đó, ví dụ đơn hàng đang ở "Chờ thanh toán", "Đã thanh toán", "Đang giao".
- Transition (chuyển đổi): hành động dịch chuyển từ state này sang state khác, ví dụ từ "Chờ thanh toán" sang "Đã thanh toán".
- Event / Trigger (sự kiện kích hoạt): cái gì làm transition xảy ra — khách bấm nút, hệ thống nhận webhook thanh toán, hết thời gian chờ.
- Guard / Condition (điều kiện): ràng buộc phải thỏa mãn thì transition mới được phép, ví dụ "chỉ được hủy đơn khi đơn chưa được đóng gói".
Vì sao mỗi đối tượng nghiệp vụ cần một state machine
Hầu hết các đối tượng cốt lõi trong hệ thống doanh nghiệp đều có vòng đời (lifecycle): đơn hàng, hợp đồng, hồ sơ KYC, yêu cầu hoàn tiền, ticket hỗ trợ, khoản vay. Chúng "sinh ra", đi qua nhiều giai đoạn, rồi "kết thúc". Khi bạn mô hình hóa vòng đời này bằng state machine, bạn buộc phải trả lời những câu hỏi mà nếu bỏ sót sẽ thành bug production:
- Có bao nhiêu trạng thái tất cả? (Đừng quên các trạng thái lỗi, hủy, hết hạn.)
- Trạng thái đầu tiên là gì? Trạng thái kết thúc (final state) là gì?
- Từ mỗi trạng thái được đi tới đâu, và không được đi tới đâu?
- Ai/cái gì kích hoạt mỗi transition?
State đối lập với screen
Một cạm bẫy tư duy của Designer: nhầm màn hình với trạng thái. Một trạng thái nghiệp vụ có thể hiển thị trên nhiều màn hình khác nhau (đơn "Đang giao" xuất hiện ở app khách, ở dashboard vận hành, ở màn hình shipper). Ngược lại, một màn hình có thể hiển thị nhiều trạng thái (màn "Chi tiết đơn hàng" hiển thị đủ mọi trạng thái đơn). State machine sống ở tầng dữ liệu/nghiệp vụ, độc lập với UI. Đây là cú "nâng cấp" tư duy quan trọng: bạn đang mô tả bản chất nghiệp vụ, không phải cái vỏ giao diện.
Tình huống thực tế
Ví dụ 1 — Đơn hàng của một sàn TMĐT như Tiki
Hãy tưởng tượng bạn là BA cho đội đơn hàng của một sàn TMĐT nội địa quy mô như Tiki. Đối tượng "Đơn hàng" có các state:
Chờ thanh toán → Đã thanh toán → Đang đóng gói → Đang giao → Giao thành công
Cộng thêm các nhánh: Hủy (từ khách hoặc hệ thống) và Hoàn tiền.
Khi bạn ngồi họp với dev, câu hỏi đầu tiên họ hỏi là: "Từ trạng thái nào thì được hủy?". Nếu bạn trả lời được ngay bằng một bảng — "Được hủy từ Chờ thanh toán và Đã thanh toán; không được hủy khi đã Đang giao trở đi, lúc đó phải dùng luồng Hoàn trả" — thì bạn đã tiết kiệm cho dự án hàng giờ tranh luận.
Con số thực tế: giả sử mỗi ngày sàn xử lý 50.000 đơn, chỉ cần 0,5% đơn rơi vào trạng thái "kẹt" (ví dụ đã thanh toán nhưng webhook đóng gói không chạy, đơn treo mãi ở "Đã thanh toán") là đã có 250 đơn/ngày cần CSKH can thiệp thủ công. Chính state machine giúp bạn phát hiện sớm: bạn định nghĩa một transition có timeout — nếu đơn ở "Đã thanh toán" quá 24 giờ mà chưa sang "Đang đóng gói" thì tự động chuyển sang state "Cần rà soát" và bắn cảnh báo.
Bài học: state machine không chỉ mô tả happy path, nó là nơi bạn cài đặt các "van an toàn" (timeout, trạng thái ngoại lệ) để hệ thống tự phát hiện đơn kẹt thay vì để khách phàn nàn.
Ví dụ 2 — Hồ sơ KYC tại một ví điện tử như MoMo hoặc ZaloPay
Ở một ví điện tử, mỗi người dùng có một hồ sơ eKYC (định danh khách hàng điện tử) với vòng đời:
Chưa nộp → Chờ duyệt → Đã duyệt — và nhánh quan trọng Bị từ chối.
Đây là ví dụ kinh điển về giá trị của việc mô hình hóa transition quay lui. Khi hồ sơ "Bị từ chối" (ví dụ ảnh CCCD mờ), khách phải được nộp lại. Câu hỏi nghiệp vụ: khi nộp lại, trạng thái quay về đâu? Về "Chờ duyệt" hay tạo hồ sơ mới? Và giới hạn số lần nộp lại là bao nhiêu?
Giả sử phòng Compliance ra luật: mỗi khách được nộp lại tối đa 3 lần; sau lần từ chối thứ 3, hồ sơ chuyển sang state Khóa định danh và bắt buộc lên quầy/gọi tổng đài. Nếu bạn — với tư cách BA — không mô hình hóa guard "số lần từ chối < 3" trên transition "nộp lại", thì dev sẽ để khách nộp vô hạn, và bạn tạo ra một lỗ hổng rửa tiền/gian lận mà đội Risk sẽ "hỏi thăm" bạn.
Con số minh họa: nếu ví có 2 triệu người dùng cần eKYC, tỷ lệ từ chối lần đầu thường 8–12% (ảnh mờ, sai thông tin). Với 10%, bạn có 200.000 hồ sơ đi vào nhánh "Bị từ chối". Cách bạn thiết kế transition quay lui ảnh hưởng trực tiếp đến tỷ lệ hoàn tất định danh và cả tuân thủ pháp lý.
Bài học: các state ngoại lệ (Bị từ chối, Khóa) và transition quay lui thường mang giá trị nghiệp vụ và rủi ro lớn hơn cả happy path. Đây chính là nơi BA tạo ra khác biệt so với một Designer chỉ vẽ luồng đẹp.
Ví dụ 3 — Hợp đồng tại một công ty bảo hiểm
Ở một công ty bảo hiểm nhân thọ, đối tượng "Hợp đồng" có vòng đời dài và nhiều nhánh:
Bản chào (Draft) → Chờ thẩm định → Đã phát hành (Active) → ...
Từ "Active", hợp đồng có thể đi tới nhiều state song song về mặt nghiệp vụ: Đang khiếu nại/bồi thường, Nợ phí (chưa đóng phí kỳ), Tạm ngưng hiệu lực (lapse), Khôi phục, Đáo hạn, Chấm dứt. Một số state có thời hạn: hợp đồng "Nợ phí" quá 60 ngày ân hạn sẽ tự động sang "Tạm ngưng hiệu lực".
Điểm thú vị: một hợp đồng "Tạm ngưng" vẫn có thể "Khôi phục" trong vòng 24 tháng nếu khách đóng bù phí — một transition quay lui có guard về thời gian. Nếu bạn bỏ sót state "Tạm ngưng" và gộp luôn vào "Chấm dứt", công ty sẽ mất đi khả năng khôi phục hàng nghìn hợp đồng mỗi năm — tương đương doanh thu phí bảo hiểm bị bỏ lỡ có thể lên tới nhiều tỷ đồng.
Bài học: với các domain phức tạp (bảo hiểm, ngân hàng), state machine trở thành "bản đồ nghiệp vụ" mà cả phòng ban chia sẻ. BA là người giữ bản đồ đó chính xác và cập nhật.
Hướng dẫn từng bước
Đây là quy trình bạn có thể áp dụng cho bất kỳ đối tượng nghiệp vụ nào.
Bước 1 — Xác định đối tượng và phạm vi. Chọn đúng một đối tượng ("Đơn hàng", "Hợp đồng", "Ticket"). Đừng gộp nhiều đối tượng vào một state machine. Nếu bạn thấy mình đang mô tả trạng thái của hai thứ cùng lúc (ví dụ trạng thái đơn và trạng thái thanh toán), hãy tách thành hai state machine liên kết với nhau.
Bước 2 — Liệt kê tất cả state. Brainstorm mọi trạng thái, gồm cả trạng thái đầu (initial), trạng thái cuối (final), và các trạng thái ngoại lệ (lỗi, hủy, hết hạn). Mẹo: hỏi CSKH và đội vận hành — họ biết những trạng thái "bẩn" mà tài liệu chính thức không ghi.
Bước 3 — Đặt tên state rõ ràng, ở dạng "đã hoàn tất một việc". Dùng tên trạng thái (danh từ/tính từ), không phải tên hành động. "Đã thanh toán" tốt hơn "Thanh toán". Tránh tên mơ hồ như "Đang xử lý" — xử lý gì, ở khâu nào?
Bước 4 — Vẽ transition và gắn event. Với mỗi cặp state, hỏi: có được đi từ A sang B không? Nếu có, event nào kích hoạt? Vẽ mũi tên, ghi nhãn event lên mũi tên (ví dụ mũi tên "Chờ thanh toán → Đã thanh toán" gắn event "nhận webhook thanh toán thành công").
Bước 5 — Thêm guard/condition. Với mỗi transition, hỏi: có điều kiện nào phải thỏa mãn không? "Chỉ hủy được khi chưa đóng gói", "chỉ khôi phục được trong 24 tháng". Ghi guard trong ngoặc vuông trên mũi tên.
Bước 6 — Lập bảng ma trận trạng thái (state transition matrix). Đây là công cụ mạnh nhất để đưa cho dev. Kẻ bảng với hàng là state hiện tại, cột là state đích, ô giao là điều kiện/ghi chú (hoặc "X" nếu cấm). Ma trận này ép bạn xét mọi cặp, kể cả những cặp bạn "quên".
Ví dụ một phần ma trận đơn hàng:
| Từ \ Sang | Đã thanh toán | Đang giao | Hủy |
|---|---|---|---|
| Chờ thanh toán | nhận thanh toán | — (cấm) | khách/hệ thống hủy |
| Đã thanh toán | — | sau đóng gói | được, hoàn tiền |
| Đang giao | — | — | — (cấm, dùng Hoàn trả) |
Về công cụ vẽ: bạn có thể dùng ngay Mermaid (cú pháp stateDiagram-v2) để nhúng sơ đồ vào Confluence/Jira dưới dạng code, dễ cập nhật; hoặc draw.io/Miro nếu muốn kéo thả trực quan. Là cựu Designer, bạn có lợi thế lớn ở khâu này — hãy tận dụng nhưng đừng để sơ đồ đẹp che mất tính đầy đủ của logic.
Lỗi thường gặp & mẹo
Lỗi 1 — Chỉ vẽ happy path. Đây là bệnh kinh điển của Designer chuyển sang BA. Bạn vẽ luồng đẹp mà quên các nhánh hủy, lỗi, hết hạn, quay lui. Mẹo: sau khi vẽ xong, tự hỏi từng state một câu "nếu ở đây mà xảy ra chuyện xấu thì đi đâu?".
Lỗi 2 — Nhầm state với screen. Đã nói ở trên. Mẹo kiểm tra: nếu một "state" của bạn thay đổi khi bạn đổi thiết kế UI mà dữ liệu không đổi, thì đó là screen chứ không phải state nghiệp vụ.
Lỗi 3 — Bỏ quên transition bị cấm. Requirement chỉ ghi cái được phép, không ghi cái cấm. Dev sẽ tự suy diễn, và mỗi người suy diễn một kiểu. Mẹo: dùng ma trận trạng thái, đánh dấu rõ ô "X" cho mọi transition cấm.
Lỗi 4 — State machine có "đường cụt" ngoài ý muốn. Một state mà không có transition nào đi ra (dù nó không phải final state) — đối tượng bị "kẹt" vĩnh viễn ở đó. Mẹo: rà từng non-final state, đảm bảo có ít nhất một lối ra hợp lệ.
Lỗi 5 — Gộp quá nhiều đối tượng. Trộn trạng thái đơn hàng với trạng thái thanh toán và trạng thái giao vận vào một sơ đồ khổng lồ. Mẹo: tách thành nhiều state machine nhỏ, mỗi cái một đối tượng, rồi mô tả cách chúng liên kết (event của cái này kích hoạt transition của cái kia).
Mẹo vàng: luôn định nghĩa một state "lỗi/cần rà soát" làm nơi hứng các tình huống bất thường, kèm cơ chế đưa đối tượng trở lại luồng bình thường. Điều này biến hệ thống từ "im lặng lỗi" thành "chủ động báo lỗi".
Bài tập thực hành
Chọn một trong hai đối tượng sau và hoàn thành đầy đủ:
Đề A — Yêu cầu hoàn tiền (Refund) của một sàn TMĐT. Khách tạo yêu cầu hoàn tiền cho một đơn đã giao. Yêu cầu đi qua: chờ CSKH duyệt, có thể bị từ chối, có thể được duyệt và chuyển sang xử lý hoàn tiền, hoàn tiền thành công hoặc thất bại (phải thử lại).
Đề B — Ticket hỗ trợ kỹ thuật nội bộ. Ticket từ lúc tạo, được phân công, đang xử lý, chờ phản hồi khách, giải quyết xong, đóng — và có thể mở lại nếu khách chưa hài lòng.
Với đối tượng bạn chọn, hãy hoàn thành:
- Liệt kê tất cả state (tối thiểu 5, nhớ gồm state ngoại lệ và final).
- Xác định initial state và (các) final state.
- Vẽ sơ đồ state machine (giấy, Miro, hoặc Mermaid
stateDiagram-v2). - Với mỗi transition, ghi rõ event kích hoạt và guard (nếu có).
- Lập ma trận trạng thái đầy đủ, đánh dấu rõ các ô "cấm".
- Viết ra 3 transition bị cấm quan trọng nhất và giải thích vì sao chúng phải bị cấm về mặt nghiệp vụ.
Tóm tắt
State machine là công cụ giúp bạn — một cựu Designer — nâng cấp trực giác vẽ luồng thành đặc tả nghiệp vụ chặt chẽ. Mỗi đối tượng nghiệp vụ (đơn hàng, hợp đồng, KYC, ticket, refund) có một tập state hữu hạn và các transition được kiểm soát bởi event và guard. Khác với flowchart mô tả trình tự bước, state machine mô tả đối tượng đang ở đâu và được phép đi tới đâu — và quan trọng không kém, không được đi tới đâu.
Ba điều cần nhớ: (1) đừng chỉ vẽ happy path — giá trị BA nằm ở các state ngoại lệ và transition quay lui; (2) phân biệt rõ state nghiệp vụ với màn hình UI; (3) dùng ma trận trạng thái để ép mình xét mọi cặp chuyển đổi, gồm cả những cái bị cấm. Khi bạn giao cho dev một state machine đầy đủ kèm ma trận, bạn không chỉ mô tả tính năng — bạn đang bảo vệ hệ thống khỏi cả một lớp bug mà nếu không có tư duy trạng thái sẽ lọt ra tận production.