Before You Begin
- Go 1.21+ (or your preferred language โ examples in Go)
- protoc (protobuf compiler) and protoc-gen-go, protoc-gen-go-grpc
- Docker (for RabbitMQ or NATS)
- curl or Postman for REST testing
- Basic understanding of microservices architecture
We'll build three services: coral-inventory, coral-orders, and coral-payments. The ocean creatures theme is just for fun โ it won't sacrifice clarity.
The Problem: How Should Services Talk?
Your e-commerce platform needs to check inventory when an order is placed, then process payment, and finally update inventory. Classic request-response? Or fire-and-forget? The wrong choice leads to cascading failures, tight coupling, or impossible scaling.
A common pitfall, frequently discussed on StackOverflow, is assuming one protocol fits all. Many developers start with REST everywhere, then hit latency issues (e.g., waiting for payment confirmation before responding to the user). The official Microservices Communication: gRPC vs REST vs Message Queues documentation recommends evaluating each interaction's requirements.
1. REST as the Baseline
REST over HTTP is the jack-of-all-trades. For our coral-inventory service, we expose a GET endpoint to check stock. Simple, but every call is synchronous and payloads are JSON.
// inventory/main.go
http.HandleFunc("/stock", func(w http.ResponseWriter, r *http.Request) {
// returns JSON: {"item":"whale-toothpaste","quantity":5}
})Engineering blogs from companies like Netflix often note that REST is fine for external APIs but inefficient for internal high-frequency calls. The text-based serialization adds overhead, and there's no built-in contract enforcement.
Problem: Every millisecond counts when you're checking stock for 10 items in a single order. REST's verbosity and lack of strong typing lead to runtime errors.
2. gRPC for High-Performance Internal Calls
A pattern seen in production Go repositories on GitHub is to use gRPC for service-to-service communication where latency matters. Define a .proto file:
syntax = "proto3";
package coral;
service Inventory {
rpc CheckStock (StockRequest) returns (StockResponse);
}
message StockRequest {
string item_id = 1;
repeated string sku = 2; // whale-blue, whale-red
}
message StockResponse {
map<string, int32> quantities = 1;
}Generate Go code with protoc --goout=. --go-grpcout=. inventory.proto. Then implement the server:
// inventory/server.go
type inventoryServer struct {
coral.UnimplementedInventoryServer
}
func (s *inventoryServer) CheckStock(ctx context.Context, req *coral.StockRequest) (*coral.StockResponse, error) {
// query DB, return map
}The client (order service) calls grpc.Dial and gets a stub. gRPC uses HTTP/2, binary serialization, and supports streaming. The official gRPC documentation emphasizes that this reduces latency by 5-10x over REST for small payloads.
Problem solved? Not entirely. gRPC is synchronous โ if the inventory service is down, the order service blocks. For critical paths, you need fallbacks.
3. Message Queues for Decoupling
When the order is placed, we don't need an immediate response from payment. We can fire an event and let the payment service process asynchronously. This is where message queues shine.
Many developers on StackOverflow encounter errors when configuring message queues โ wrong exchange types, missing acknowledgments, or duplicate messages. We'll use NATS (lightweight, easy to run in Docker):
docker run -p 4222:4222 nats:latestIn the order service, publish an event:
// orders/publisher.go
nc, _ := nats.Connect(nats.DefaultURL)
subject := "order.created"
data := `{"order_id":"123","items":[{"sku":"whale-blue","qty":2}]}`
nc.Publish(subject, []byte(data))In the payment service, subscribe:
// payments/subscriber.go
nc.Subscribe("order.created", func(m *nats.Msg) {
// process payment, then publish "payment.completed"
})This decouples the services. If payment is slow, orders still succeed quickly. The downside: eventual consistency and added complexity (message ordering, retries, dead-letter queues).
4. When to Use Each: The Decision Matrix
| Interaction Type | Example | Recommended Pattern | Why? | |-----------------|---------|---------------------|------| | Real-time stock check | Order service needs inventory | gRPC | Low latency, strong contract | | External API for mobile app | Get product details | REST | Universal, HTTP cache-friendly | | Payment processing | After order placed | Message Queue | Asynchronous, fault-tolerant | | Bulk data sync | Nightly catalog update | gRPC streaming | Efficient, no polling |
A common pattern seen in architecture blogs on Medium is to use a hybrid approach: gRPC for synchronous internal calls, REST for external APIs, and message queues for event-driven workflows.
5. Building the Hybrid Architecture
Let's wire our e-commerce platform:
- REST API Gateway (
coral-gateway) โ accepts HTTP requests from clients, translates to gRPC calls to internal services. - gRPC Inventory Service (
coral-inventory) โ returns stock levels. - Order Service (
coral-orders) โ uses gRPC to check stock, then publishesorder.createdto NATS. - Payment Service (
coral-payments) โ subscribes toorder.created, processes payment, publishespayment.completed. - Inventory Update Service (
coral-updater) โ subscribes topayment.completedand decrements stock via gRPC.
Code example for the gateway calling inventory via gRPC:
// gateway/main.go
func getStock(w http.ResponseWriter, r *http.Request) {
conn, _ := grpc.Dial("inventory:50051", grpc.WithInsecure())
defer conn.Close()
client := coral.NewInventoryClient(conn)
resp, _ := client.CheckStock(context.Background(), &coral.StockRequest{ItemId: "whale-toothpaste"})
json.NewEncoder(w).Encode(resp.Quantities)
}Notice we added a REST endpoint for external clients but gRPC internally. This is a standard pattern in production systems at companies like Square (as seen in their engineering blogs).
6. Common Issues and Their Solutions
gRPC Connection Refused
- Cause: Service not running or port mismatch. In Go,
grpc.DialwithWithBlock()hangs if unreachable. - Fix: Use
grpc.WithDefaultCallOptions(grpc.WaitForReady(false))and implement retries with exponential backoff.
Message Queue Duplicate Messages
- Cause: At-least-once delivery (NATS default).
- Solution: Make consumers idempotent. Use a unique
order_idand check if already processed in a database.
REST Timeout in Chained Calls
- Cause: Order service calls payment service via REST and waits.
- Solution: Replace with async queue, or use circuit breakers (like Hystrix) for synchronous calls.
Protobuf Field Order Errors
- Cause: Changing proto field numbers without migration.
- Fix: Never reuse field numbers. Follow official protobuf best practices (field numbers are immutable).
Wrapping Up
You now have a concrete decision framework and code for three communication patterns. The key is to match the pattern to the interaction's requirements: latency, coupling, and fault tolerance. Start with REST for external surfaces, gRPC for internal high-throughput calls, and message queues for asynchronous workflows. The ocean creatures e-commerce platform is your sandbox โ adapt it to your own production needs.

Memuat komentar...