DEV Community

Young Gao
Young Gao

Posted on

Microservices Communication: REST, gRPC, and Message Queues

Microservices Communication: REST, gRPC, and Message Queues

Your microservices call each other via HTTP. One service goes down. Cascading failures bring everything down. Here is how to pick the right communication pattern.

Synchronous: REST and gRPC

REST: Simple, universal, human-readable. Best for CRUD operations and public APIs.

gRPC: Binary protocol (Protocol Buffers), type-safe, bidirectional streaming. Best for internal service-to-service calls where performance matters.

// REST: ~50ms per call, text-based
GET /api/users/123

// gRPC: ~5ms per call, binary, auto-generated clients
service UserService {
  rpc GetUser (GetUserRequest) returns (User);
}
Enter fullscreen mode Exit fullscreen mode

Asynchronous: Message Queues

For operations that do not need an immediate response:

// Producer: fire and forget
await queue.add("send-email", { to: user.email, template: "welcome" });
res.json({ status: "accepted" }); // Respond immediately

// Consumer: processes independently
worker.process("send-email", async (job) => {
  await sendEmail(job.data);
});
Enter fullscreen mode Exit fullscreen mode

When to Use What

Pattern Use When Avoid When
REST CRUD, public APIs, simple integrations High-throughput internal calls
gRPC Internal services, streaming, low latency Browser clients, simple APIs
Message Queue Async tasks, event-driven, decoupling Need immediate response
Event Sourcing Audit trails, complex workflows Simple CRUD apps

Key Principles

  • Default to async (message queues) unless you need a synchronous response
  • Use circuit breakers for all synchronous calls
  • Make all operations idempotent
  • Design for partial failure: one service being down should not crash everything

Part of my Production Backend Patterns series. Follow for more practical backend engineering.

Top comments (0)