Communication Styles — Giao tiếp Microservices(Building Microservices)

Phong

Mở đầu

Ảnh: MART PRODUCTION — Pexels

Khi chuyển từ monolithic sang microservices, câu hỏi đầu tiên mà bất kỳ team nào cũng phải đối mặt là: các service này nói chuyện với nhau bằng cách nào? Trong chương 4 của cuốn "Building Microservices: Designing Fine-Grained Systems", Sam Newman dành trọn vẹn để phân tích các communication styles — những kiểu giao tiếp khác nhau giữa các microservices, từ ưu điểm, nhược điểm cho đến tình huống áp dụng cụ thể.

Bài viết này sẽ tóm tắt 3 pattern cốt lõi mà Newman trình bày: Request/Response (giao tiếp đồng bộ), Event-Driven (giao tiếp bất đồng bộ qua message broker), và Hybrid/Service Discovery — kết hợp cả hai. Mỗi pattern đều có trade-off riêng, và việc chọn sai kiểu giao tiếp có thể dẫn đến những hậu quả khó lường khi hệ thống mở rộng.

Request/Response — Giao tiếp đồng bộ

Ảnh: Timothy Huliselan — Pexels

Đây là kiểu giao tiếp quen thuộc nhất: service A gửi request đến service B và chờ response. Trong thế giới microservices, pattern này thường xuất hiện dưới dạng REST (HTTP API truyền thống), gRPC (HTTP/2 + Protocol Buffers) hoặc GraphQL.

Newman chỉ ra rằng điểm mạnh của Request/Response là đơn giản, dễ hiểu, dễ debug. Bạn gọi API, nhận kết quả, xử lý tiếp — flow rất trực quan. Nhưng điểm yếu lớn nhất là coupling về thời gian (temporal coupling): nếu service B chậm hoặc chết, service A cũng bị ảnh hưởng trực tiếp. Đây là lý do tại sao các pattern như circuit breaker, retry with backoff, và timeout trở nên cực kỳ quan trọng khi áp dụng Request/Response trong microservices.

Một lưu ý quan trọng khác: Newman cảnh báo về việc lạm dụng synchronous call. Nếu service A gọi B, B gọi C, C gọi D — một chain đồng bộ kiểu này làm tăng latency, giảm độ tin cậy, và biến một lỗi nhỏ ở service cuối thành sập cả hệ thống. Giải pháp là luôn đặt câu hỏi: "Có cần response ngay lập tức không?" Nếu không, hãy cân nhắc chuyển sang async.

Event-Driven — Giao tiếp bất đồng bộ

Ảnh: Markus Spiske — Pexels

Ngược lại với Request/Response, Event-Driven Communication cho phép các service giao tiếp một cách gián tiếp thông qua một message broker (Kafka, RabbitMQ, NATS, v.v.). Service A publish event lên broker, service B (và C, D) subscribe để nhận event đó — A không cần biết B có tồn tại hay không.

Newman trình bày hai hình thức chính của event-driven:

  • Notification: Service A thông báo một sự kiện đã xảy ra (VD: "order_created"), các service khác tự quyết định xem có làm gì với event đó không. Đây là dạng fire-and-forget — A không quan tâm ai nhận hay kết quả ra sao.
  • Event Carrying State: Event chứa đầy đủ dữ liệu cần thiết, giúp service nhận không cần gọi lại service gốc. Ví dụ, event "order_created" chứa luôn thông tin customer, sản phẩm, giá — thay vì chỉ có order_id và bắt service nhận phải gọi API lấy thêm.

Điểm mạnh lớn nhất của event-driven là loose coupling — các service không phụ thuộc trực tiếp vào nhau, có thể scale, deploy, và fail độc lập. Tuy nhiên, nó cũng đi kèm với độ phức tạp cao hơn: cần infrastructure riêng cho message broker, phải xử lý eventual consistency (dữ liệu có thể chưa đồng bộ ngay lập tức), và khó debug hơn khi có lỗi.

Service Discovery & Hybrid Patterns

Một vấn đề thực tế khi triển khai microservices là: làm sao service A biết địa chỉ (IP + port) của service B? Trong môi trường container/dynamic, địa chỉ thay đổi liên tục. Newman giới thiệu Service Discovery như một giải pháp: dùng một registry trung tâm (Consul, etcd, Kubernetes DNS) để các service đăng ký và tra cứu địa chỉ của nhau.

Ngoài ra, Newman cũng nhấn mạnh rằng không có pattern nào là tối ưu cho mọi tình huống. Hầu hết hệ thống microservices thành công đều dùng hybrid approach: Request/Response cho các operation cần response ngay (VD: kiểm tra số dư tài khoản), và Event-Driven cho các operation có thể xử lý sau (VD: gửi email xác nhận, cập nhật điểm thưởng).

Key Takeaways

  • Request/Response đơn giản, dễ debug nhưng dễ bị temporal coupling — luôn dùng timeout, circuit breaker, và tránh chain đồng bộ dài
  • Event-Driven giúp loose coupling và độc lập về thời gian, nhưng phức tạp hơn và cần quen với eventual consistency
  • Service Discovery là hạ tầng bắt buộc trong môi trường động (container/K8s) — không dùng static config
  • Luôn đặt câu hỏi "Có cần response ngay không?" trước khi chọn synchronous
  • Hybrid approach (kết hợp cả 2 pattern) là thực tế phổ biến nhất trong production

Kết

Chương 4 của Building Microservices mang đến một góc nhìn thực tế về communication styles — không chỉ là lý thuyết suông mà đi kèm với những trade-off cụ thể mà backend engineer phải đối mặt hàng ngày. Điểm đáng giá nhất là lời khuyên của Newman: đừng cứng nhắc chọn một pattern cho toàn bộ hệ thống. Hãy hiểu rõ yêu cầu của từng use case và chọn pattern phù hợp.

Bài tiếp theo trong series sẽ nói về Chapter 5 — Versioning & Evolution, cách quản lý sự thay đổi của API trong môi trường microservices mà không làm chậm đội nhóm. Hẹn gặp lại các bạn!