Chương 22. Chọn Cách Giao Tiếp Đúng

Từ chương 17 đến chương 21, ta đã đi qua khá nhiều cách giao tiếp:

  • HTTP/API.
  • Queue.
  • Pub/Sub.
  • Event Streaming.
  • Webhook.
  • Polling.
  • SSE.
  • WebSocket.

Nếu nhìn riêng từng món, chúng đều có vẻ hợp lý.

Nhưng khi thiết kế hệ thống thật, câu hỏi không phải là:

> Công nghệ nào hay nhất?

Câu hỏi đúng hơn là:

> Với việc này, bên nào cần nói với bên nào, cần kết quả nhanh đến đâu, có được xử lý sau không, có bao nhiêu bên cần biết, và nếu lỗi thì sao?

Chọn cách giao tiếp đúng không bắt đầu từ tên công nghệ.

Nó bắt đầu từ bản chất của việc cần làm.

Ví dụ AI Judge:

Học viên nộp bài
-> cần trả response nhanh
-> tạo job chấm nền
-> worker gọi Gemini
-> lưu kết quả
-> frontend biết kết quả
-> learning/notification/analytics cùng phản ứng

Trong một luồng này, ta có thể dùng nhiều cách giao tiếp:

Frontend -> Backend: HTTP API
Backend -> Worker: Queue
Worker -> Gemini: HTTP API
GradingCompleted -> nhiều bên: Pub/Sub/Event
Frontend xem kết quả: Polling hoặc SSE

Không có một công cụ duy nhất giải quyết mọi thứ.

Thiết kế tốt là biết đặt từng công cụ vào đúng chỗ.

---

22.1. Câu hỏi đầu tiên: có cần kết quả ngay không?

Đây là câu hỏi quan trọng nhất.

Nếu bên gọi cần kết quả ngay để tiếp tục, thường dùng HTTP/API hoặc một kiểu request/response tương tự.

Ví dụ:

Frontend cần đăng nhập.
Frontend cần lấy danh sách bài học.
Admin cần xem chi tiết đơn.
Backend cần lấy payment_url để redirect user.

Những việc này cần câu trả lời ngay.

Client -> API -> Response

Nhưng nếu việc lâu và không cần kết quả cuối ngay, đừng bắt request chờ.

Ví dụ:

Chấm bài AI 90 giây.
Tạo báo cáo 2 phút.
Resize video.
Gửi email hàng loạt.
Đồng bộ CRM.

Khi đó:

HTTP nhận yêu cầu
-> tạo job
-> đưa vào queue
-> trả job_id

Một câu nhớ nhanh:

> Cần câu trả lời ngay thì API. Cần làm lâu thì queue.

---

22.2. Câu hỏi thứ hai: đây là việc cần làm hay sự kiện đã xảy ra?

Nếu message có nghĩa:

Hãy làm việc này.

thì nó giống command/job.

Ví dụ:

RunGradingJob
SendEmail
GenerateReport
ResizeImage
SyncCustomerToCrm

Queue thường phù hợp.

Nếu message có nghĩa:

Việc này đã xảy ra.

thì nó là event.

Ví dụ:

OrderPlaced
PaymentSucceeded
GradingCompleted
DeliveryFailed
RefundIssued

Pub/Sub hoặc Event Streaming thường phù hợp nếu nhiều bên cần biết.

Đừng đặt tên event như job.

Không tốt:

SendEmail
UpdateAnalytics
CreateInvoice

Tốt hơn:

OrderPlaced
PaymentSucceeded
GradingCompleted

Rồi consumer tự quyết định gửi email, cập nhật analytics, tạo invoice.

---

22.3. Câu hỏi thứ ba: có bao nhiêu bên cần biết?

Nếu chỉ có một worker cần xử lý một việc:

RunGradingJob(job_123)

dùng queue.

Nếu nhiều bên cùng cần phản ứng với một sự kiện:

GradingCompleted

dùng Pub/Sub hoặc Event Streaming.

Ví dụ:

GradingCompleted
-> Learning cập nhật tiến độ
-> Notification báo học viên
-> Analytics ghi chi phí AI
-> Certificate kiểm tra điều kiện cấp chứng chỉ

Nếu RunGradingJob bị hai worker xử lý cùng lúc, có thể là lỗi.

Nếu GradingCompleted được nhiều consumer xử lý, đó là mục tiêu.

Một câu nhớ:

> Một việc cho một nhóm worker: queue. Một sự kiện cho nhiều bên nghe: Pub/Sub.

---

22.4. Câu hỏi thứ tư: có cần lưu lịch sử và replay không?

Nếu event chỉ cần báo cho các bên hiện tại xử lý, Pub/Sub bền có thể đủ.

Nếu event cần trở thành lịch sử để:

  • Consumer mới đọc lại.
  • Rebuild read model.
  • Tính lại analytics.
  • Rebuild search index.
  • Lưu vào data lake.
  • Replay sau khi sửa bug.

thì nghĩ đến Event Streaming.

Ví dụ:

ProductUpdated
-> search index cập nhật

Nếu index hỏng và bạn muốn đọc lại toàn bộ lịch sử product event để rebuild, event streaming hữu ích.

Ví dụ AI Judge:

GradingCompleted

Ban đầu chỉ cần notification và learning update.

Pub/Sub có thể đủ.

Sau này muốn phân tích:

  • Mỗi lớp tốn bao nhiêu chi phí AI.
  • Rubric nào gây lỗi nhiều.
  • Thời gian chấm trung bình theo model.
  • Điểm trung bình theo assignment.

Lúc đó event stream lịch sử có giá trị hơn.

Nhưng nếu chưa cần replay, đừng vội Kafka.

---

22.5. Câu hỏi thứ năm: client cần cập nhật trạng thái kiểu gì?

Khi frontend cần biết một việc đã xong, có vài lựa chọn.

Nếu đơn giản:

Polling

Frontend hỏi định kỳ.

Ví dụ:

GET /grading-jobs/{id}

Nếu muốn server đẩy một chiều:

SSE

Ví dụ server báo:

grading_completed

Nếu cần hai chiều realtime:

WebSocket

Ví dụ chat, game, collaborative editor.

Một câu nhớ:

> Job status thường bắt đầu bằng polling. Cần mượt hơn thì SSE. Cần hai chiều realtime thật thì WebSocket.

---

22.6. Câu hỏi thứ sáu: đây là server-to-server callback không?

Nếu một hệ thống ngoài cần báo lại cho backend của ta, thường dùng webhook.

Ví dụ:

Payment Gateway -> Backend: payment_succeeded
Delivery Provider -> Backend: delivery_failed
GitHub -> CI System: push event
AI Judge Service -> Main App: grading_completed

Webhook nghĩa là:

> Bên kia làm xong hoặc có sự kiện, rồi gọi HTTP về endpoint của ta.

Webhook cần:

  • Verify signature.
  • Idempotency.
  • Log raw event.
  • Trả response nhanh.
  • Retry/DLQ nếu xử lý sau.
  • Reconciliation nếu nghiệp vụ quan trọng.

Không dùng WebSocket cho chuyện này.

Webhook là server gọi server.

WebSocket là client-server giữ kết nối realtime hai chiều.

---

22.7. Một bản đồ quyết định nhanh

| Nhu cầu | Lựa chọn thường hợp | |---|---| | Cần dữ liệu ngay để hiển thị | HTTP API | | Cần thực hiện command ngắn | HTTP API | | Việc lâu, xử lý sau | Queue | | Một job chỉ một worker xử lý | Queue | | Một sự kiện nhiều bên cần biết | Pub/Sub | | Event quan trọng không được mất | Durable Pub/Sub + outbox | | Cần lưu lịch sử/replay | Event Streaming | | Frontend kiểm tra job xong chưa | Polling | | Frontend nhận update một chiều | SSE | | Client-server realtime hai chiều | WebSocket | | Hệ thống ngoài báo lại cho ta | Webhook | | Dữ liệu đọc tổng hợp từ nhiều service | Read model, có thể build bằng event | | Analytics từ nhiều sự kiện | Event Streaming hoặc event pipeline |

Bảng này không thay thế suy nghĩ.

Nó chỉ giúp ta không chọn sai quá sớm.

---

22.8. Ví dụ 1: nộp bài AI

Yêu cầu:

Học viên nộp bài.
Bài cần chấm bằng AI.
Chấm mất khoảng 90 giây.
Học viên cần xem kết quả khi xong.
Learning, Notification, Analytics cũng cần biết.

Thiết kế hợp lý:

Frontend -> Backend:
  HTTP POST /submissions

Backend:
  tạo Submission
  tạo GradingJob(PENDING)
  enqueue RunGradingJob
  trả 202 Accepted + job_id

Worker:
  lấy job từ queue
  gọi Gemini API
  lưu kết quả
  phát GradingCompleted

Frontend:
  polling GET /grading-jobs/{id}
  hoặc SSE nhận grading_completed

Consumers:
  Learning nghe GradingCompleted
  Notification nghe GradingCompleted
  Analytics nghe GradingCompleted

Ở đây:

  • HTTP dùng để nhận request và đọc trạng thái.
  • Queue dùng cho việc chấm lâu.
  • Pub/Sub dùng cho nhiều bên phản ứng sau khi chấm xong.
  • Polling/SSE dùng cho frontend.
  • Database lưu trạng thái thật.

Không nên:

Frontend POST /submit
-> Backend chờ Gemini 90 giây
-> Backend cập nhật learning, gửi email, analytics trong cùng request

Vì request quá lâu và quá nhiều việc phụ dính vào nhau.

---

22.9. Ví dụ 2: thanh toán đơn hàng

Yêu cầu:

Khách checkout.
Cần tạo payment.
Payment gateway sẽ báo kết quả.
Order, Accounting, Notification cần biết khi thanh toán thành công.

Thiết kế hợp lý:

Frontend -> Backend:
  POST /checkout

Backend:
  tạo Order
  tạo PaymentIntent qua Payment Gateway
  trả payment_url

Payment Gateway:
  gọi webhook /webhooks/payment

Backend webhook:
  verify signature
  lưu event/payment state
  phát PaymentSucceeded

Consumers:
  Ordering cập nhật Order = PAID
  Accounting ghi doanh thu
  Notification gửi biên nhận

Frontend có thể:

Polling GET /orders/{id}
hoặc được redirect về trang kết quả
hoặc nhận SSE/WebSocket notification nếu có

Không nên tin frontend nói:

Tôi thanh toán rồi.

Trạng thái payment phải đến từ webhook đã verify hoặc query provider để đối soát.

---

22.10. Ví dụ 3: gửi email xác nhận

Yêu cầu:

Sau khi user đăng ký, gửi email chào mừng.

Nếu hệ thống nhỏ:

RegisterUserUseCase
-> tạo user
-> enqueue SendWelcomeEmail

Queue là đủ.

Nếu nhiều bên cũng cần biết user mới:

UserRegistered
-> Email
-> Analytics
-> CRM
-> Recommendation

Pub/Sub hợp hơn.

Email consumer có thể tạo job:

UserRegistered
-> enqueue SendWelcomeEmail

Không cần Event Streaming nếu không cần replay lịch sử user event.

Nếu sau này analytics cần phân tích toàn bộ hành vi user, event stream có thể xuất hiện.

---

22.11. Ví dụ 4: dashboard realtime

Yêu cầu:

Admin muốn xem số đơn, doanh thu, lỗi payment gần realtime.

Nếu cập nhật mỗi 30 giây là đủ:

Polling

Nếu muốn server đẩy số liệu mỗi vài giây:

SSE

Nếu dashboard có tương tác hai chiều phức tạp:

WebSocket

Dữ liệu phía sau có thể đến từ:

Events -> analytics/read model -> dashboard API/SSE

Không nên để dashboard trực tiếp kéo dữ liệu nặng từ nhiều service mỗi giây nếu có nhiều admin mở cùng lúc.

---

22.12. Ví dụ 5: chat

Yêu cầu:

Hai người nhắn tin realtime.
Typing indicator.
Online/offline.
Read receipt.

WebSocket thường phù hợp.

Luồng:

Client A -> WebSocket -> Server: message
Server -> lưu message DB
Server -> WebSocket -> Client B: message mới

Nếu nhiều server:

Pub/Sub backend để fanout đến server đang giữ connection

Nhưng message vẫn cần lưu database.

WebSocket chỉ là kênh realtime.

Nó không thay thế storage.

---

22.13. Ví dụ 6: cập nhật search index

Yêu cầu:

Khi product thay đổi, search index phải cập nhật.

Nếu đơn giản:

ProductUpdated
-> Search consumer cập nhật index

Pub/Sub bền có thể đủ.

Nếu cần rebuild index từ lịch sử event:

Product events stream
-> replay để build lại index

Event Streaming hữu ích.

Search index là read model, không phải nguồn sự thật.

Nếu index lỗi, có thể rebuild từ database hoặc replay event nếu lịch sử đủ.

---

22.14. Đừng dùng HTTP cho mọi thứ

HTTP dễ hiểu nên rất dễ bị lạm dụng.

Ví dụ xấu:

Order API
-> Payment API
-> Accounting API
-> Notification API
-> Analytics API
-> CRM API

Tất cả nằm trên đường request checkout.

Hậu quả:

  • Request chậm.
  • Một service phụ lỗi làm luồng chính lỗi.
  • Retry khó.
  • Timeout dây chuyền.
  • Debug khó.

Cách tốt hơn:

Checkout làm phần cần thiết ngay.
Các phản ứng phụ đi qua event/queue.

HTTP nên dùng cho câu hỏi hoặc command cần kết quả ngay.

Không nên dùng để kéo mọi việc phụ vào cùng một request.

---

22.15. Đừng dùng Queue cho mọi thứ

Queue cũng dễ bị lạm dụng.

Ví dụ xấu:

LoginRequest -> Queue -> Worker -> LoginResult

Login cần kết quả ngay.

Queue làm trải nghiệm kỳ quặc.

Hoặc:

GET product detail -> Queue

Không hợp.

Queue dùng cho việc nền, không dùng cho truy vấn dữ liệu trực tiếp.

Một câu nhớ:

> Queue tốt cho "làm sau", không tốt cho "trả lời ngay".

---

22.16. Đừng dùng Pub/Sub để giấu luồng nghiệp vụ

Pub/Sub giúp giảm coupling, nhưng cũng có thể làm luồng khó lần theo.

Ví dụ:

EventA -> Handler1 -> EventB -> Handler2 -> EventC -> Handler3

Không ai nhìn được toàn bộ checkout flow.

Nếu luồng dài và quan trọng, có thể cần:

  • Workflow/Saga rõ.
  • Event catalog.
  • Trace theo correlation_id.
  • Dashboard trạng thái.
  • Documentation luồng.

Pub/Sub không thay thế thiết kế use case.

Nó chỉ là cách các phần phản ứng với sự kiện.

---

22.17. Đừng dùng Event Streaming quá sớm

Event Streaming mạnh, nhưng không nhẹ.

Nếu hệ thống chỉ có:

  • Vài job nền.
  • Một vài event.
  • Không cần replay.
  • Không có analytics lớn.
  • Team chưa vận hành broker.

thì Kafka hoặc streaming platform có thể quá sớm.

Bắt đầu bằng:

Database + Queue + Outbox + Pub/Sub bền đơn giản

có thể đủ tốt.

Khi thật sự cần:

  • Nhiều consumer.
  • Lịch sử event.
  • Replay.
  • Data pipeline.
  • Analytics realtime.

lúc đó Event Streaming đáng giá hơn.

---

22.18. Đừng dùng WebSocket khi Polling đủ

WebSocket nghe có vẻ hiện đại.

Nhưng nó kéo theo:

  • Connection lâu dài.
  • Reconnect.
  • Heartbeat.
  • Auth theo channel.
  • Scale nhiều server.
  • Pub/Sub backend.
  • Monitoring connection.

Nếu việc chỉ là:

Job xong chưa?

Polling mỗi 3-5 giây thường đủ.

Nếu muốn mượt hơn, SSE là bước giữa tốt.

WebSocket nên dành cho:

  • Chat.
  • Collaboration.
  • Game.
  • Presence.
  • Tương tác hai chiều realtime thật.

---

22.19. Một luồng có thể dùng nhiều cách giao tiếp

Đừng cố ép toàn bộ hệ thống dùng một kiểu.

Ví dụ đặt bánh:

Frontend -> API:
  HTTP POST /orders

Backend -> Payment:
  HTTP để tạo payment_url

Payment Gateway -> Backend:
  Webhook báo payment succeeded

Backend:
  publish PaymentSucceeded

Consumers:
  Ordering, Accounting, Notification nghe event

Notification:
  enqueue SendEmail
  nếu user online, gửi SSE/WebSocket signal

Frontend:
  polling hoặc SSE để thấy trạng thái order

Mỗi đoạn có bản chất khác nhau.

Vì vậy mỗi đoạn có công cụ khác nhau.

Kiến trúc tốt không phải là dùng ít tên công nghệ nhất.

Kiến trúc tốt là mỗi đoạn dùng đúng công cụ cho nhu cầu của nó.

---

22.20. Luôn hỏi về lỗi

Chọn giao tiếp đúng phải hỏi:

> Nếu bên kia lỗi thì sao?

Với HTTP:

  • Timeout bao lâu?
  • Retry không?
  • Có idempotency không?
  • Có fallback không?

Với Queue:

  • Retry mấy lần?
  • DLQ ở đâu?
  • Job có idempotent không?
  • Backlog tăng thì sao?

Với Pub/Sub:

  • Event có mất không?
  • Consumer fail thì sao?
  • Event trùng thì sao?
  • Lag có được đo không?

Với Webhook:

  • Verify signature chưa?
  • Provider retry thì sao?
  • Event đến sai thứ tự thì sao?
  • Có reconciliation không?

Với SSE/WebSocket:

  • Mất connection thì sao?
  • Client bỏ lỡ event thì đồng bộ lại thế nào?
  • Có API lấy state hiện tại không?

Một thiết kế giao tiếp chưa nói về lỗi là thiết kế chưa xong.

---

22.21. Luôn hỏi về nguồn sự thật

Trong hệ thống nhiều cách giao tiếp, dễ bị mờ nguồn sự thật.

Ví dụ:

Payment status thật nằm ở đâu?
Order status thật nằm ở đâu?
Grading result thật nằm ở đâu?
Notification đã đọc thật nằm ở đâu?

Realtime message không nên là nguồn sự thật duy nhất.

Queue message không nên là nguồn sự thật duy nhất nếu job cần trạng thái.

Event stream có thể là lịch sử, nhưng trạng thái hiện tại thường vẫn cần read model/database.

Hãy luôn trả lời:

> Nếu mọi connection mất, mọi consumer restart, user mở lại sau 1 giờ, hệ thống đọc trạng thái thật từ đâu?

Câu trả lời thường là:

Database hoặc read model bền vững.

---

22.22. Luôn hỏi về độ trễ chấp nhận được

Không phải mọi thứ cần realtime.

Ví dụ:

Payment confirmation: càng nhanh càng tốt, vài giây.
AI grading: vài phút có thể chấp nhận tùy sản phẩm.
Analytics dashboard: chậm 1-5 phút có thể ổn.
Email marketing: chậm 30 phút có thể ổn.
Typing indicator: cần rất nhanh nhưng mất cũng không sao.

Độ trễ quyết định công cụ.

Nếu chậm vài giây được:

Polling/Queue/Eventual consistency

Nếu phải gần realtime một chiều:

SSE/PubSub

Nếu phải hai chiều:

WebSocket

Nếu phải đúng ngay trong cùng thao tác:

HTTP/API + transaction cục bộ

---

22.23. Luôn hỏi về độ quan trọng

Cùng là message, nhưng mức quan trọng khác nhau.

Ví dụ:

PaymentSucceeded

Không được mất.

Cần outbox, durable broker, idempotency, reconciliation.

UserTyping

Mất cũng không sao.

Có thể dùng Pub/Sub tạm thời hoặc WebSocket signal.

Analytics event

Tùy hệ thống, có thể mất một ít hoặc cần rất chắc.

Không nên dùng cùng một mức độ bảo đảm cho mọi thứ.

Việc quan trọng cần bền và có đối soát.

Việc tạm thời cần nhanh và nhẹ.

---

22.24. Luôn hỏi về scale

Một lựa chọn đúng ở 100 user có thể sai ở 1 triệu user.

Polling mỗi 5 giây với 100 user:

20 request/giây

Ổn.

Polling mỗi 1 giây với 1 triệu user:

1.000.000 request/giây

Không ổn.

WebSocket với 1.000 connection khác WebSocket với 1 triệu connection.

Queue với 100 job/ngày khác queue với 1 triệu job/giờ.

Event streaming chỉ đáng khi volume và nhu cầu dữ liệu xứng đáng.

Đừng thiết kế quá lớn từ đầu, nhưng cũng đừng bỏ qua con số.

---

22.25. Bảng so sánh tổng hợp

| Cách | Dùng khi | Tránh khi | Rủi ro chính | |---|---|---|---| | HTTP/API | Cần kết quả ngay, query, command ngắn | Việc lâu, nhiều phản ứng phụ | Timeout, retry, gọi dây chuyền | | Queue | Việc lâu, retry, kiểm soát concurrency | Cần response ngay | Backlog, duplicate, job kẹt | | Pub/Sub | Một event nhiều bên nghe | Luồng đơn giản một consumer | Khó debug, event mất nếu không bền | | Event Streaming | Cần lịch sử, replay, analytics | Hệ thống nhỏ, không cần replay | Vận hành, schema, lag, replay sai | | Webhook | Server ngoài báo lại | Frontend realtime | Giả mạo, duplicate, sai thứ tự | | Polling | Client kiểm tra trạng thái đơn giản | Cần realtime cao, user cực lớn | Tốn request | | SSE | Server push một chiều | Cần hai chiều liên tục | Connection lâu, proxy buffering | | WebSocket | Hai chiều realtime | Chỉ job status đơn giản | Scale connection, reconnect, auth |

---

22.26. Bảng quyết định theo câu hỏi

| Câu hỏi | Nếu câu trả lời là có | Thường chọn | |---|---|---| | Người gọi cần câu trả lời ngay? | Có | HTTP/API | | Việc mất lâu? | Có | Queue | | Chỉ một worker nên xử lý? | Có | Queue | | Nhiều bên cần biết một sự kiện? | Có | Pub/Sub | | Cần đọc lại lịch sử event? | Có | Event Streaming | | Bên ngoài cần gọi báo về? | Có | Webhook | | Frontend chỉ cần kiểm tra job status? | Có | Polling | | Server cần đẩy update một chiều? | Có | SSE | | Client/server nói chuyện liên tục hai chiều? | Có | WebSocket | | Message không được mất? | Có | Durable broker + outbox | | Message có thể trùng? | Gần như luôn có | Idempotency |

---

22.27. Một checklist trước khi chốt thiết kế

Trước khi chọn cách giao tiếp, hãy hỏi:

  • Ai là bên gửi?
  • Ai là bên nhận?
  • Đây là command, query, job hay event?
  • Có cần response ngay không?
  • Có thể xử lý sau không?
  • Có bao nhiêu consumer?
  • Có cần lưu lịch sử không?
  • Có cần replay không?
  • Có cần realtime UI không?
  • Một chiều hay hai chiều?
  • Nếu message mất thì hậu quả gì?
  • Nếu message trùng thì sao?
  • Nếu xử lý chậm thì sao?
  • Nếu bên nhận down thì sao?
  • Nguồn sự thật nằm ở đâu?
  • Trạng thái hiện tại đọc từ đâu?
  • Có cần retry, DLQ, reconciliation không?
  • Có cần idempotency không?
  • Có cần observability gì?

Nếu trả lời được, chọn công cụ sẽ dễ hơn rất nhiều.

---

22.28. Tóm tắt bằng một câu chuyện

Hãy lấy một hệ thống học online có AI Judge.

Học viên nộp bài:
  HTTP API

Chấm bài mất 90 giây:
  Queue

Worker gọi Gemini:
  HTTP API ra external provider

Chấm xong:
  GradingCompleted event

Learning, Notification, Analytics cùng cần biết:
  Pub/Sub

Analytics muốn phân tích lịch sử:
  Event Streaming nếu scale đủ lớn

Frontend cần xem trạng thái:
  Polling trước, SSE nếu muốn mượt hơn

AI Judge tách service riêng báo về Main App:
  Webhook hoặc event integration

Chat giữa học viên và mentor:
  WebSocket

Cùng một sản phẩm, nhiều cách giao tiếp cùng tồn tại.

Đó là bình thường.

Quan trọng là mỗi cách nằm đúng chỗ.

---

22.29. Kết luận của chương

Chọn cách giao tiếp đúng là một kỹ năng kiến trúc rất quan trọng.

Không có công cụ nào thắng mọi trường hợp.

HTTP/API rõ ràng và trực tiếp, nhưng không nên ôm việc lâu.

Queue giúp xử lý nền, retry và kiểm soát concurrency, nhưng làm hệ thống bất đồng bộ hơn.

Pub/Sub giúp nhiều bên phản ứng với một sự kiện, nhưng cần idempotency và observability.

Event Streaming mạnh khi cần lịch sử và replay, nhưng không nên dùng quá sớm.

Webhook rất hợp cho server-to-server callback, nhưng phải verify và xử lý trùng.

Polling đơn giản và đủ tốt trong nhiều trường hợp.

SSE hợp với server push một chiều.

WebSocket hợp với realtime hai chiều thật sự.

Thông điệp quan trọng nhất:

> Đừng hỏi "nên dùng công nghệ nào?". Hãy hỏi "việc này cần giao tiếp theo kiểu nào?".

Khi hiểu bản chất của việc cần làm, lựa chọn công nghệ sẽ bớt mơ hồ hơn rất nhiều.

Và khi chọn đúng cách giao tiếp, hệ thống không chỉ chạy được, mà còn dễ debug, dễ mở rộng, dễ vận hành, và dễ giải thích cho người khác.