Product Management
Đăng nhập
ESC

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

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

M-Bài 39 — Integration patterns — Synchronous Asynchronous Event-driven

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

Khi bạn từ Marketing chuyển sang BA, có một khoảnh khắc gần như chắc chắn sẽ xảy ra trong cuộc họp đầu tiên với team kỹ thuật: ai đó nói "cái này mình gọi sync hay async?", "event này bắn lên rồi consumer xử lý sau", hoặc "API bên payment trả về timeout thì xử lý thế nào?". Nếu bạn không hiểu những từ này, bạn sẽ ngồi gật gù mà không nắm được điều quan trọng nhất: cách các hệ thống nói chuyện với nhau quyết định trải nghiệm của người dùng cuối.

Đây không phải kiến thức "của dev". Là một BA, bạn không cần biết viết code để gọi API, nhưng bạn bắt buộc phải hiểu ba kiểu giao tiếp giữa các hệ thống — Synchronous (đồng bộ), Asynchronous (bất đồng bộ), và Event-driven (hướng sự kiện). Lý do rất thực tế: lựa chọn pattern tích hợp ảnh hưởng trực tiếp tới yêu cầu nghiệp vụ mà bạn viết. Một màn hình thanh toán "chờ kết quả ngay" và một màn hình "đặt lệnh rồi báo kết quả qua thông báo" là hai trải nghiệm hoàn toàn khác nhau, và chính bạn là người mô tả hành vi mong muốn đó trong tài liệu.

Tin tốt cho dân Marketing: bạn đã quen với khái niệm "đồng bộ vs bất đồng bộ" hơn bạn nghĩ. Gửi email marketing và chờ phản hồi từng người chính là async. Một popup chat live đòi trả lời ngay chính là sync. Webhook của HubSpot bắn dữ liệu lead sang CRM khi có form submit chính là event-driven. Bài này sẽ giúp bạn hệ thống hóa trực giác đó thành ngôn ngữ kỹ thuật mà bạn có thể dùng tự tin trong tài liệu BA và trong các buổi làm việc với dev.

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

Hãy hình dung mọi hệ thống phần mềm ngày nay đều là một tập hợp nhiều "dịch vụ" (service) phải trao đổi thông tin: app mobile gọi server, server gọi cổng thanh toán, cổng thanh toán báo về ngân hàng, hệ thống gửi SMS xác nhận... Cách chúng "gọi nhau" rơi vào ba mô hình chính.

1. Synchronous — Request/Response (gọi và chờ)

Trong mô hình đồng bộ, bên gọi (caller) gửi yêu cầu rồi đứng chờ cho tới khi nhận được phản hồi mới làm tiếp. Giống như bạn gọi điện cho tổng đài: bạn không cúp máy cho tới khi nhân viên trả lời xong.

Ví dụ kinh điển là REST API: app gọi GET /user/123, server trả về thông tin người dùng ngay lập tức, app hiển thị lên màn hình. Caller bị "khóa" (blocked) trong lúc chờ.

Đặc điểm:

  • Phản hồi tức thì, người dùng thấy kết quả ngay.
  • Nếu bên kia chậm hoặc lỗi, caller cũng bị treo theo — nên cần timeout (giới hạn thời gian chờ).
  • Hai hệ thống bị coupling chặt (phụ thuộc lẫn nhau): bên kia chết thì luồng của bạn cũng đứng.
Dùng khi: cần kết quả ngay để đi tiếp, ví dụ kiểm tra số dư trước khi cho đặt lệnh, đăng nhập, tra cứu thông tin.

2. Asynchronous — Fire-and-forget hoặc gọi rồi nhận sau

Trong mô hình bất đồng bộ, caller gửi yêu cầu rồi không đứng chờ kết quả — nó làm tiếp việc khác. Kết quả (nếu có) sẽ về sau, qua một cơ chế khác như callback, webhook, hoặc người dùng tự kiểm tra lại.

Có hai biến thể bạn nên phân biệt:

  • Fire-and-forget: gửi xong là xong, không quan tâm kết quả. Ví dụ: ghi log, đẩy một sự kiện tracking sang hệ thống analytics.
  • Request-async-response: gửi yêu cầu, nhận một "biên nhận" ngay (kiểu "đã nhận, đang xử lý"), kết quả thật trả về sau qua kênh riêng. Ví dụ: bạn nộp hồ sơ vay online, hệ thống báo "đã tiếp nhận", vài phút sau mới có thông báo duyệt/từ chối.
Async thường dùng message queue (hàng đợi tin nhắn) như RabbitMQ, Kafka, hay AWS SQS làm trung gian. Người gửi bỏ tin vào hàng đợi, người nhận lấy ra xử lý theo nhịp của mình.

Đặc điểm:

  • Caller không bị treo, hệ thống chịu tải tốt hơn, đỉnh tải được "san phẳng".
  • Coupling lỏng hơn: bên xử lý chết tạm thời thì tin nhắn vẫn nằm trong hàng đợi, xử lý lại sau.
  • Đổi lại: phức tạp hơn về trải nghiệm — phải thiết kế trạng thái "đang xử lý", phải có cơ chế báo kết quả về.

3. Event-driven — Hướng sự kiện (publish/subscribe)

Đây là mức nâng cao của async. Thay vì A gọi thẳng B, A chỉ phát ra một sự kiện ("Đơn hàng đã được tạo") và không cần biết ai sẽ nghe. Các hệ thống quan tâm sẽ đăng ký lắng nghe (subscribe) sự kiện đó và tự phản ứng.

Ví dụ: khi sự kiện OrderPlaced được phát ra, ba hệ thống cùng lắng nghe và mỗi hệ thống làm một việc: hệ thống kho trừ tồn, hệ thống email gửi xác nhận, hệ thống loyalty cộng điểm. Bên phát sự kiện không hề biết có ba bên này, và việc thêm bên thứ tư sau này (ví dụ chống gian lận) không cần sửa code bên phát.

Đặc điểm:

  • Coupling cực lỏng: bên phát và bên nghe không biết nhau, dễ mở rộng.
  • Phù hợp hệ thống lớn, nhiều microservice, nhiều luồng nghiệp vụ song song.
  • Đổi lại: khó debug, khó truy vết "tại sao điều này xảy ra", thứ tự xử lý không đảm bảo, và phải tính tới chuyện một sự kiện bị xử lý hai lần (idempotency).
Một cách dễ nhớ dành cho dân Marketing: Synchronous giống gọi điện trực tiếp; Asynchronous giống nhắn tin rồi chờ người ta rảnh trả lời; Event-driven giống đăng một bài lên group — ai quan tâm thì tự đọc và tự hành động, bạn không nhắm tới ai cụ thể.

Tình huống thực tế

Ví dụ 1 — MoMo và bài toán thanh toán: sync hay async?

Giả sử bạn là BA tại một ví điện tử kiểu MoMo, làm tính năng "Thanh toán hóa đơn điện EVN". Câu hỏi đầu tiên trong buổi refinement: luồng gọi sang nhà cung cấp điện nên là sync hay async?

Phân tích: việc trừ tiền trong ví phải synchronous — người dùng cần biết ngay tiền đã bị trừ hay chưa, không thể để màn hình quay vòng 30 giây rồi mới báo. Nhưng việc ghi nhận thanh toán bên EVN lại thường asynchronous: hệ thống EVN có thể nhận lệnh, trả về "đã tiếp nhận", và xác nhận "đã gạch nợ thành công" qua một callback sau vài giây tới vài phút.

Hậu quả nếu thiết kế sai: nếu BA ép toàn bộ luồng phải sync và chờ EVN xác nhận, chỉ cần EVN chậm (giả sử trung bình 8 giây, đỉnh tải lễ Tết lên 40 giây), người dùng sẽ thấy app "đơ", nhiều người bấm lại nhiều lần dẫn tới trừ tiền hai lần. Đây là sự cố nghiệp vụ nghiêm trọng.

Bài học rút ra: BA phải tách rõ trong tài liệu hai trạng thái — "Thanh toán đang xử lý" (đã trừ tiền, chờ đối tác xác nhận) và "Thanh toán thành công" (đối tác đã xác nhận). Bạn cần viết yêu cầu cho màn hình hiển thị trạng thái trung gian, cho thông báo push khi có kết quả cuối, và cho cơ chế hoàn tiền tự động nếu đối tác báo thất bại. Đây chính là phần "request-async-response" trong thực tế.

Ví dụ 2 — Tiki và sự kiện "Đặt hàng thành công"

Hình dung bạn làm BA cho một sàn TMĐT như Tiki, phụ trách luồng sau khi khách bấm "Đặt hàng". Ban đầu, hệ thống cũ làm theo kiểu synchronous tuần tự: tạo đơn → gọi kho trừ tồn → gọi service gửi email → gọi service cộng điểm → mới báo thành công cho khách. Vấn đề: nếu service gửi email bị chậm 5 giây, khách hàng phải chờ thêm 5 giây dù việc gửi email chẳng liên quan gì tới việc đơn có được tạo hay không. Tệ hơn, nếu service cộng điểm bị lỗi, cả đơn hàng fail dù tiền và hàng đều ổn.

Đội kiến trúc đề xuất chuyển sang event-driven: khi đơn được tạo, hệ thống chỉ phát sự kiện OrderPlaced và báo thành công ngay cho khách trong khoảng 300ms. Ba service kho, email, loyalty tự lắng nghe và xử lý độc lập, song song, sau đó.

Vai trò của BA ở đây cực kỳ quan trọng: bạn phải định nghĩa đâu là việc bắt buộc xong trước khi báo thành công (trừ tồn kho để tránh bán quá số lượng) và đâu là việc có thể làm sau (email, điểm thưởng). Bạn cũng phải viết yêu cầu cho tình huống "email gửi lỗi" — không được làm hỏng đơn, mà phải retry và cảnh báo nội bộ. Bài học: event-driven cho phép trải nghiệm nhanh hơn nhiều, nhưng đổi lại BA phải mô tả rõ ràng từng nhánh xử lý lỗi, vì các việc không còn diễn ra theo một đường thẳng nữa.

Ví dụ 3 — Webhook ngân hàng và bài học từ chính nền Marketing của bạn

Một startup fintech ở TP.HCM tích hợp với cổng thanh toán để nhận tiền chuyển khoản qua VietQR. Cổng thanh toán dùng cơ chế webhook — một dạng event-driven async: khi có giao dịch tới, cổng sẽ gọi ngược (HTTP POST) về một URL của startup để báo "đã nhận tiền đơn X". Đây chính xác là cùng cơ chế mà bạn từng dùng khi cho Facebook Lead Ads bắn webhook lead về CRM hồi còn làm Marketing.

Sự cố xảy ra: webhook đôi khi bị gửi trùng lặp (cổng gửi lại vì không nhận được phản hồi xác nhận kịp), khiến hệ thống cộng tiền hai lần cho một đơn. BA non kinh nghiệm đã không lường trước điều này.

Bài học rút ra: với mọi tích hợp event-driven/webhook, BA phải đặt ra yêu cầu về idempotency — mỗi sự kiện có một mã giao dịch duy nhất, và hệ thống phải đảm bảo xử lý cùng một mã nhiều lần cũng chỉ cho ra một kết quả. Đây là một yêu cầu phi chức năng (non-functional requirement) mà nếu bạn không viết ra, dev rất dễ bỏ sót và tiền thật sẽ bị tính sai. Trực giác từ Marketing — "webhook có thể bắn trùng, phải lọc trùng lead" — chính là cùng một nguyên lý ở quy mô nghiêm trọng hơn.

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

Khi bạn đối diện một tích hợp giữa hai hệ thống và cần quyết định pattern, hãy đi theo trình tự sau để đưa vào tài liệu BA:

  • Xác định người dùng có cần kết quả ngay không. Nếu trải nghiệm đòi hỏi phản hồi tức thì để đi bước tiếp theo (đăng nhập, kiểm tra số dư, tra cứu), nghiêng về synchronous. Nếu kết quả có thể tới sau mà không cản trở người dùng, nghiêng về asynchronous.
  • Đánh giá độ tin cậy và tốc độ của hệ thống bên kia. Nếu đối tác chậm hoặc không ổn định (cổng thanh toán bên thứ ba, hệ thống legacy của ngân hàng), tránh sync trực tiếp để không kéo cả luồng của bạn sập theo. Dùng async với hàng đợi để cô lập rủi ro.
  • Đếm số bên cần phản ứng với một hành động. Nếu một sự kiện kích hoạt nhiều luồng độc lập (đặt hàng → kho, email, điểm, chống gian lận), cân nhắc event-driven để mỗi bên tự xử lý và dễ thêm bên mới sau này.
  • Định nghĩa rõ các trạng thái trung gian. Với async/event-driven, luôn có trạng thái "đang xử lý". Viết yêu cầu cho UI hiển thị trạng thái này, và cho cách người dùng nhận kết quả cuối (push, email, refresh màn hình).
  • Mô tả xử lý lỗi và timeout cho từng nhánh. Sync: điều gì xảy ra khi quá thời gian chờ? Async: tin nhắn lỗi sẽ retry mấy lần, sau đó vào "hàng đợi chết" (dead-letter queue) để con người xử lý? Event-driven: nếu một consumer fail, các consumer khác có bị ảnh hưởng không?
  • Đặt yêu cầu idempotency cho mọi luồng async/webhook. Mỗi giao dịch một mã duy nhất; xử lý lặp không gây hậu quả kép.
  • Vẽ sequence diagram đơn giản. Một sơ đồ thể hiện ai gọi ai, mũi tên đặc (sync) và mũi tên đứt (async/event) sẽ giúp dev và stakeholder hiểu nhanh hơn ba trang chữ.

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

Lỗi 1 — Mặc định mọi thứ là synchronous. Đây là lỗi phổ biến nhất của BA mới. Vì sync dễ hình dung nhất ("gọi rồi có kết quả"), người ta áp nó cho cả những việc đáng lẽ phải async, dẫn tới màn hình treo và phụ thuộc chéo dễ sập. Mẹo: với mỗi lời gọi sang hệ thống ngoài, tự hỏi "người dùng có thật sự cần chờ kết quả này không?".

Lỗi 2 — Quên trạng thái "đang xử lý". Khi chuyển sang async, nhiều BA chỉ mô tả hai trạng thái "thành công" và "thất bại", bỏ quên trạng thái trung gian. Hậu quả là dev tự bịa hành vi, hoặc UI không có gì để hiển thị trong lúc chờ. Luôn liệt kê đủ: chờ gửi → đang xử lý → thành công/thất bại.

Lỗi 3 — Bỏ sót idempotency và xử lý trùng. Như ví dụ webhook ngân hàng, không lường chuyện sự kiện bị gửi trùng sẽ gây sai lệch dữ liệu tài chính. Mẹo: mặc định coi mọi webhook và message đều có thể tới hai lần, và viết yêu cầu chống trùng ngay từ đầu.

Lỗi 4 — Dùng event-driven cho việc đơn giản. Event-driven mạnh nhưng phức tạp, khó debug, khó truy vết. Đừng "đú trend microservice" cho một luồng chỉ có hai hệ thống nói chuyện. Mẹo: chọn pattern đơn giản nhất đáp ứng được yêu cầu, đừng over-engineer.

Mẹo vàng cho BA: bạn không cần quyết định pattern thay cho kiến trúc sư, nhưng bạn phải mô tả đúng hành vi nghiệp vụ mong muốn để họ chọn pattern phù hợp. Câu hỏi của BA luôn là "trải nghiệm và quy tắc nghiệp vụ là gì", còn "dùng Kafka hay REST" là của dev. Tận dụng thế mạnh Marketing của bạn: bạn vốn rất nhạy với trải nghiệm người dùng và hành trình khách hàng — đó chính là lăng kính tốt nhất để phân biệt khi nào cần chờ, khi nào không.

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

Bài 1 — Phân loại pattern. Với mỗi tình huống sau, hãy xác định pattern phù hợp (sync / async / event-driven) và giải thích ngắn lý do:

  • a) Người dùng nhập OTP để đăng nhập.
  • b) Sau khi mua khóa học, hệ thống gửi email biên lai, cấp quyền truy cập, và cộng điểm thành viên.
  • c) App đẩy sự kiện "người dùng xem sản phẩm X" sang hệ thống analytics để gợi ý về sau.
  • d) Người dùng nộp hồ sơ vay tín chấp online và chờ kết quả thẩm định.
Bài 2 — Viết yêu cầu trạng thái. Lấy tình huống MoMo thanh toán hóa đơn điện ở trên. Hãy liệt kê đầy đủ các trạng thái của một giao dịch và mô tả UI/thông báo tương ứng cho từng trạng thái, kèm cách xử lý khi đối tác báo thất bại sau khi đã trừ tiền.

Bài 3 — Bắt lỗi idempotency. Một dev viết: "Khi nhận webhook báo thanh toán, cộng số tiền vào ví người dùng." Hãy chỉ ra rủi ro nghiệp vụ trong câu này và viết lại thành một yêu cầu an toàn hơn, có tính tới webhook gửi trùng.

Bài 4 — Vẽ sơ đồ. Vẽ một sequence diagram đơn giản cho luồng "đặt hàng trên sàn TMĐT" theo kiểu event-driven, dùng mũi tên đặc cho sync và mũi tên đứt cho event. Đánh dấu đâu là việc bắt buộc xong trước khi báo thành công cho khách.

(Gợi ý tự chấm: ở Bài 1, đáp án là a-sync, b-event-driven, c-async fire-and-forget, d-async request-response.)

Tóm tắt

Ba pattern tích hợp là ngôn ngữ nền tảng để bạn làm việc với đội kỹ thuật. Synchronous là gọi và chờ — dùng khi cần kết quả ngay, nhưng coupling chặt và dễ bị treo theo bên kia. Asynchronous là gửi rồi không chờ, kết quả về sau qua hàng đợi hoặc callback — chịu tải tốt, cô lập rủi ro, đổi lại phải thiết kế trạng thái trung gian. Event-driven là phát sự kiện cho nhiều bên cùng nghe — cực kỳ linh hoạt và dễ mở rộng, nhưng khó debug và bắt buộc phải lo idempotency.

Là BA chuyển từ Marketing, bạn có lợi thế: trực giác về trải nghiệm người dùng giúp bạn phân biệt "khi nào người dùng cần chờ". Nhiệm vụ của bạn không phải chọn công nghệ, mà là mô tả đúng hành vi nghiệp vụ, các trạng thái, và cách xử lý lỗi để đội kiến trúc chọn pattern phù hợp. Hãy luôn ghi nhớ ba câu hỏi cốt lõi: người dùng có cần kết quả ngay không, có bao nhiêu bên cần phản ứng, và chuyện gì xảy ra khi mọi thứ đi sai. Nắm chắc những điều này, bạn sẽ tự tin ngồi trong mọi buổi refinement kỹ thuật.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi