Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là BA của một dự án ví điện tử. Trong buổi grooming, Dev hỏi: "Khi user bấm thanh toán mà mạng rớt giữa chừng, hệ thống xử lý thế nào? Tiền đã trừ chưa? Trạng thái giao dịch là gì?". Nếu lúc đó bạn chỉ có vài dòng mô tả kiểu "user thanh toán thành công", bạn sẽ ấp úng. Còn nếu bạn mở ra một Sequence Diagram thể hiện rõ từng bước giao tiếp giữa MobileApp, API Gateway, Payment Service, và một State Machine mô tả vòng đời của giao dịch (PENDING → AUTHORIZED → CAPTURED → SETTLED, hoặc rẽ nhánh sang FAILED, REFUNDED), thì cả phòng sẽ gật gù: "À, BA này hiểu hệ thống".
Đó chính là lý do Bài 35 quan trọng. Là Technical BA, hai loại sơ đồ này là vũ khí mạnh nhất để bạn diễn đạt hành vi động (dynamic behavior) của hệ thống — thứ mà bảng requirement dạng văn xuôi không bao giờ thể hiện hết được. Sequence Diagram trả lời câu hỏi "ai gọi ai, theo thứ tự thời gian nào", còn State Machine trả lời "một đối tượng có thể ở những trạng thái nào và chuyển đổi ra sao". Hai sơ đồ này bổ sung cho nhau: một cái nhìn theo trục thời gian giữa các thành phần, một cái nhìn theo vòng đời của một thực thể. Nắm vững chúng, bạn viết spec ít lỗ hổng, Dev ít hỏi lại, QA viết được test case từ chính sơ đồ của bạn.
Khái niệm cốt lõi
Sequence Diagram — dòng chảy theo thời gian
Sequence Diagram (sơ đồ tuần tự) mô tả cách các actor (người dùng) và các participant (hệ thống, service, database) trao đổi message với nhau theo đúng thứ tự thời gian, đọc từ trên xuống dưới.
Các thành phần cơ bản:
- Lifeline (đường đời): mỗi participant có một đường thẳng dọc kéo từ trên xuống, thể hiện sự tồn tại của nó theo thời gian. Ví dụ:
Customer,MobileApp,API Gateway,Payment Service,Bank. - Message (thông điệp): mũi tên ngang giữa các lifeline. Mũi tên đặc (
→) là lời gọi đồng bộ (synchronous — gọi xong chờ kết quả); mũi tên nét đứt (-->) là phản hồi trả về (return). Mũi tên hở thường biểu thị lời gọi bất đồng bộ (async — gửi đi rồi đi tiếp, không chờ). - Activation bar (thanh kích hoạt): hình chữ nhật mỏng trên lifeline, thể hiện khoảng thời gian một participant đang xử lý.
- Fragment (khối điều kiện): các khung như
alt(rẽ nhánh nếu/khác),opt(tùy chọn),loop(lặp),par(song song). Đây là phần mà BA non kinh nghiệm hay bỏ sót, dẫn đến sơ đồ chỉ vẽ được "happy path".
sequenceDiagram
actor Customer
participant App as MobileApp
participant GW as API Gateway
participant Pay as Payment Service
participant Bank Customer->>App: Tap "Thanh toán 250.000đ"
App->>GW: POST /payments {amount, orderId}
GW->>Pay: createPayment()
Pay->>Bank: authorize(card, amount)
alt Bank chấp thuận
Bank-->>Pay: APPROVED (authCode)
Pay-->>GW: 201 Created {status: AUTHORIZED}
GW-->>App: 201 {paymentId, status}
App-->>Customer: "Thanh toán thành công"
else Bank từ chối
Bank-->>Pay: DECLINED (reason)
Pay-->>GW: 402 {status: FAILED, reason}
GW-->>App: 402 {error}
App-->>Customer: "Thẻ bị từ chối, thử thẻ khác"
end
Chỉ với chừng đó dòng, bạn đã diễn đạt được thứ tự gọi, dữ liệu truyền đi, và cả hai nhánh thành công/thất bại — điều mà một đoạn văn dài 5 câu cũng khó truyền tải rõ bằng.
State Machine — vòng đời của một thực thể
State Machine (máy trạng thái, hay State Diagram) tập trung vào một đối tượng và trả lời: đối tượng đó có những trạng thái (state) nào, sự kiện (event/trigger) nào làm nó chuyển từ trạng thái này sang trạng thái khác (transition), và điều kiện (guard) nào phải đúng để chuyển đổi xảy ra.
Các thành phần:
- State: trạng thái rời rạc của đối tượng (ví dụ một đơn hàng:
Mới tạo,Đã xác nhận,Đang giao,Hoàn thành,Đã hủy). - Transition: mũi tên giữa hai state, gắn nhãn theo dạng
event [guard] / action. - Initial state: chấm tròn đặc, điểm bắt đầu.
- Final state: chấm tròn có viền, điểm kết thúc vòng đời.
stateDiagram-v2
[*] --> PENDING: tạo giao dịch
PENDING --> AUTHORIZED: bank approve
PENDING --> FAILED: bank decline / timeout
AUTHORIZED --> CAPTURED: merchant capture
AUTHORIZED --> VOIDED: hủy trước khi capture
CAPTURED --> SETTLED: cuối ngày đối soát
CAPTURED --> REFUNDED: yêu cầu hoàn tiền
SETTLED --> REFUNDED: yêu cầu hoàn tiền
FAILED --> [*]
VOIDED --> [*]
REFUNDED --> [*]
SETTLED --> [*]
Điểm cốt lõi mà State Machine ép bạn phải nghĩ tới: những chuyển đổi nào KHÔNG được phép xảy ra. Ví dụ, không thể từ FAILED nhảy thẳng sang CAPTURED; không thể REFUNDED rồi lại CAPTURED. Chính những "đường cấm" này là nguồn gốc của vô số bug nghiêm trọng nếu spec không nói rõ.
Khi nào dùng cái nào?
- Dùng Sequence Diagram khi bạn cần mô tả một luồng tương tác cụ thể (use case, kịch bản): đăng nhập, đặt hàng, gọi API tích hợp với đối tác.
- Dùng State Machine khi một thực thể có vòng đời phức tạp với nhiều trạng thái và quy tắc chuyển đổi: đơn hàng, hồ sơ vay, ticket hỗ trợ, giao dịch thanh toán.
Tình huống thực tế
Tình huống 1 — Ví điện tử và bài toán thanh toán "treo"
Một công ty fintech tại TP.HCM (gọi là PayQuick) ra mắt tính năng thanh toán QR. Trong sprint đầu, BA chỉ viết requirement dạng văn xuôi: "User quét QR, nhập PIN, hệ thống trừ tiền và báo thành công". Kết quả khi lên production: khoảng 0,8% giao dịch bị "treo" — tiền đã trừ ở ngân hàng nhưng app báo lỗi, khách hàng tưởng chưa thanh toán nên quét lại, dẫn đến trừ tiền hai lần. Mỗi ngày 12.000 giao dịch, 0,8% là gần 100 ca khiếu nại.
Khi điều tra, team phát hiện vấn đề nằm ở khoảng giữa: sau khi Payment Service gọi Bank.authorize() thành công nhưng trước khi phản hồi kịp về MobileApp thì kết nối rớt. BA đã không vẽ Sequence Diagram nên không nhìn ra "khoảng trống nguy hiểm" này.
Cách khắc phục: BA vẽ lại Sequence Diagram có thêm fragment xử lý timeout, và quan trọng hơn là một State Machine cho giao dịch với state AUTHORIZED rõ ràng nằm giữa PENDING và CAPTURED. Nhờ đó, team thiết kế cơ chế reconciliation (đối soát): khi app khởi động lại, nó gọi GET /payments/{orderId} để kiểm tra trạng thái thực, thay vì tạo giao dịch mới. Tỷ lệ trừ tiền hai lần giảm từ 0,8% xuống dưới 0,05%.
Bài học: phần nguy hiểm nhất của một luồng không nằm ở happy path mà ở các khoảng "ở giữa hai message". Sequence Diagram làm lộ ra chúng; State Machine bắt bạn đặt tên cho trạng thái trung gian thay vì giả định "trừ tiền là xong".
Tình huống 2 — Đơn hàng e-commerce và những chuyển đổi "không thể xảy ra"
Một sàn thương mại điện tử (giả định tên Tiki-like) gặp lỗi nghiêm trọng: có đơn hàng đã Đã hủy nhưng vẫn được hệ thống kho tạo lệnh xuất hàng và giao cho shipper. Nguyên nhân: hai service (Order Service và Warehouse Service) chạy bất đồng bộ; khi khách hủy đơn cùng lúc với việc kho nhận lệnh đóng gói, không có ai định nghĩa rõ "từ trạng thái nào thì được phép chuyển sang Đang đóng gói".
BA của dự án sau đó dựng một State Machine chuẩn cho đơn hàng:
stateDiagram-v2
[*] --> CHO_THANH_TOAN
CHO_THANH_TOAN --> DA_XAC_NHAN: thanh toán OK
CHO_THANH_TOAN --> DA_HUY: hết hạn / khách hủy
DA_XAC_NHAN --> DANG_DONG_GOI: kho nhận lệnh
DA_XAC_NHAN --> DA_HUY: khách hủy (trước đóng gói)
DANG_DONG_GOI --> DANG_GIAO: bàn giao shipper
DANG_GIAO --> HOAN_THANH: giao thành công
DANG_GIAO --> HOAN_HANG: giao thất bại
DANG_DONG_GOI --> DA_HUY: chỉ admin, có lý do
Điểm mấu chốt: từ DANG_DONG_GOI trở đi, khách không được tự hủy nữa (chỉ admin với lý do cụ thể). Quy tắc này được mã hóa thành guard condition trong code và thành business rule trong spec. Sau khi áp dụng, tỷ lệ "đơn hủy vẫn giao" về 0.
Bài học: State Machine không chỉ vẽ các đường được phép, mà giá trị lớn nhất là buộc cả team thống nhất các đường bị cấm và ai có quyền vượt rào (như admin). Mỗi transition trở thành một business rule có thể kiểm thử.
Tình huống 3 — Tích hợp đối tác và luồng webhook bất đồng bộ
Một ngân hàng số tại Đông Nam Á tích hợp với nhà cung cấp eKYC (định danh điện tử) bên thứ ba. Việc xác minh giấy tờ mất 2–30 giây, đôi khi vài phút, nên không thể gọi đồng bộ. BA cần mô tả luồng webhook bất đồng bộ này cho team backend và đối tác.
Sequence Diagram giúp làm rõ phần thường gây hiểu nhầm nhất — callback đến lúc nào:
sequenceDiagram
participant App
participant KYC as KYC Service (nội bộ)
participant Vendor as eKYC Vendor App->>KYC: POST /verify {userId, docImages}
KYC->>Vendor: submitVerification()
Vendor-->>KYC: 202 Accepted {requestId}
KYC-->>App: 202 {status: PROCESSING}
Note over Vendor: Xử lý 2s–vài phút
Vendor->>KYC: POST /webhook {requestId, result}
KYC->>KYC: cập nhật state user → VERIFIED/REJECTED
KYC-->>Vendor: 200 OK
Kèm theo là State Machine cho hồ sơ KYC: KHOI_TAO → PROCESSING → VERIFIED/REJECTED, và quan trọng là quy tắc xử lý webhook đến trễ hoặc trùng (idempotency): nếu nhận webhook cho requestId đã ở trạng thái cuối thì bỏ qua, không xử lý lại.
Bài học: với luồng async, Sequence Diagram là cách rõ ràng nhất để chỉ ra "phản hồi 202 không phải kết quả cuối cùng" — một hiểu nhầm cực kỳ phổ biến khiến frontend hiển thị sai trạng thái cho người dùng.
Hướng dẫn từng bước
Dưới đây là quy trình thực tế để bạn dựng cặp sơ đồ này cho một tính năng.
Bước 1 — Xác định phạm vi và góc nhìn. Quyết định bạn đang mô tả một luồng (dùng Sequence) hay một vòng đời thực thể (dùng State Machine), hay cả hai. Đặt tên kịch bản cụ thể, ví dụ "Thanh toán đơn hàng bằng QR".
Bước 2 — Liệt kê các participant (cho Sequence). Kê ra mọi actor và hệ thống tham gia: người dùng, frontend, gateway, từng service, database, đối tác ngoài. Đừng gộp quá thô (chỉ "hệ thống") cũng đừng chẻ quá nhỏ.
Bước 3 — Vẽ happy path trước. Đi từ trên xuống, ghi từng message theo đúng thứ tự, kèm tham số chính (POST /payments {amount, orderId}). Phân biệt rõ lời gọi đồng bộ và phản hồi trả về.
Bước 4 — Thêm các nhánh và lỗi. Bọc các tình huống rẽ nhánh trong alt/opt, lặp trong loop. Bắt buộc tự hỏi: timeout thì sao? lỗi mạng giữa chừng? đối tác trả lỗi? Đây là bước phân biệt BA giỏi.
Bước 5 — Liệt kê các trạng thái của thực thể chính (cho State Machine). Từ luồng vừa vẽ, rút ra thực thể trung tâm (giao dịch, đơn hàng) và đặt tên cho mọi trạng thái nó có thể ở, kể cả trạng thái trung gian.
Bước 6 — Vẽ transition kèm event và guard. Với mỗi cặp state, hỏi: sự kiện nào kích hoạt chuyển đổi, điều kiện nào phải đúng, ai có quyền. Ghi dạng event [guard] / action.
Bước 7 — Rà các chuyển đổi bị cấm. Lập một ma trận state-to-state, đánh dấu ô nào hợp lệ, ô nào cấm. Mỗi ô cấm là một business rule cần ghi vào spec.
Bước 8 — Review chéo với Dev/QA. Ngồi cùng Dev xác nhận khả thi kỹ thuật, cùng QA để họ biến mỗi transition thành test case. Cập nhật sơ đồ ngay tại buổi review.
Lỗi thường gặp & mẹo
- Chỉ vẽ happy path. Đây là lỗi số một. Một Sequence Diagram không có nhánh lỗi gần như vô dụng vì lỗi mới là nơi sinh ra phần lớn requirement. Luôn dành ít nhất 40% sơ đồ cho các nhánh
alt/else. - Quên trạng thái trung gian. Nhảy thẳng từ "bắt đầu" sang "hoàn thành" mà bỏ qua
AUTHORIZED,PROCESSING. Chính khoảng giữa này gây ra trừ tiền hai lần, đơn ma, webhook lạc. - Lẫn lộn message đồng bộ và bất đồng bộ. Vẽ webhook async thành mũi tên đồng bộ khiến team hiểu nhầm là sẽ có kết quả trả về ngay. Hãy phân biệt rõ và ghi chú thời gian xử lý.
- Sơ đồ quá to. Một Sequence Diagram dài 60 message thì không ai đọc nổi. Mẹo: tách thành nhiều sơ đồ con theo từng kịch bản, mỗi sơ đồ tập trung một luồng.
- State Machine thiếu final state hoặc thiếu nhánh hủy. Mọi vòng đời đều phải kết thúc ở đâu đó, và gần như luôn có nhánh "hủy/thất bại". Thiếu chúng là dấu hiệu spec chưa đầy đủ.
- Mẹo dùng "diagram as code". Dùng Mermaid hoặc PlantUML thay vì vẽ tay trong Draw.io. Sơ đồ là text nên versioning được trong Git, review được trong pull request, và sửa nhanh khi requirement đổi. Đây là chuẩn của Technical BA hiện đại.
- Mẹo gắn sơ đồ với acceptance criteria. Mỗi nhánh
altvà mỗi transition nên ánh xạ tới một dòng acceptance criteria. Khi đó sơ đồ không chỉ để minh họa mà trực tiếp lái việc viết test.
Bài tập thực hành
- Vẽ Sequence Diagram cho luồng "Đặt vé xem phim qua app": user chọn suất, chọn ghế, thanh toán qua cổng MoMo, nhận vé điện tử. Bắt buộc có ít nhất hai nhánh lỗi (ghế đã bị người khác giữ trong lúc thanh toán; thanh toán MoMo thất bại). Dùng cú pháp Mermaid.
- Vẽ State Machine cho "hồ sơ vay tiêu dùng" tại một công ty tài chính, với các trạng thái gợi ý:
NHAP_HO_SO,CHO_THAM_DINH,DUYET,TU_CHOI,DA_GIAI_NGAN,DONG_HO_SO. Ghi rõ event và guard cho từng transition, và chỉ ra ít nhất ba chuyển đổi bị cấm.
- Lập ma trận transition (bảng state x state) cho State Machine ở bài 2, đánh dấu ô hợp lệ / ô cấm, rồi viết ba business rule tương ứng với ba ô cấm bạn cho là nguy hiểm nhất nếu bị vi phạm.
- Tình huống mở rộng: lấy luồng eKYC bất đồng bộ ở Tình huống 3, bổ sung xử lý cho trường hợp webhook đến hai lần (duplicate) và webhook không bao giờ đến (vendor chết). Mô tả bằng cả Sequence Diagram lẫn quy tắc trên State Machine.
Tóm tắt
Sequence Diagram và State Machine là hai cách nhìn bổ sung về hành vi động của hệ thống — thứ mà requirement văn xuôi luôn diễn đạt thiếu. Sequence Diagram trả lời "ai gọi ai, theo thứ tự nào", làm lộ ra các khoảng trống nguy hiểm giữa các bước, đặc biệt quan trọng với luồng bất đồng bộ và tích hợp đối tác. State Machine trả lời "thực thể có những trạng thái nào, chuyển đổi ra sao", và giá trị lớn nhất của nó là buộc cả team thống nhất những chuyển đổi bị cấm — nguồn gốc của vô số bug nghiêm trọng.
Với Technical BA, hãy nhớ ba nguyên tắc: luôn vẽ nhánh lỗi chứ không chỉ happy path; luôn đặt tên cho trạng thái trung gian; và dùng "diagram as code" (Mermaid/PlantUML) để sơ đồ sống cùng codebase. Khi bạn ánh xạ mỗi nhánh và mỗi transition tới một dòng acceptance criteria, sơ đồ của bạn không còn là hình minh họa đẹp — nó trở thành bộ khung trực tiếp lái việc phát triển và kiểm thử. Đó là khác biệt giữa một BA "mô tả tính năng" và một Technical BA "thiết kế hành vi hệ thống".