Introduction
Using REST/JSON for microservice communication leads to ambiguous schemas and lost type safety. Use gRPC with Protobuf definitions for type-safe service communication. Generate designs with Claude Code.
CLAUDE.md gRPC Rules
## gRPC Design Rules
### Proto Definitions
- Centralize .proto files in monorepo's shared/proto/
- Never change field numbers (backward compatibility)
- Mark deleted fields as reserved (prevent number reuse)
### Service Design
- 1 service = 1 domain (OrderService, UserService)
- Unary RPC: standard request/response
- Server Streaming: real-time updates (progress notifications)
- Bidirectional Streaming: chat, live collaboration
### Error Handling
- Use gRPC status codes (NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED)
- Use grpc-gateway for REST conversion
- Propagate trace IDs via Metadata
Generated gRPC Implementation
// proto/order/v1/order.proto
syntax = "proto3";
package order.v1;
import "google/protobuf/timestamp.proto";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (Order);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc WatchOrderStatus(WatchOrderStatusRequest) returns (stream OrderStatusUpdate);
rpc CancelOrder(CancelOrderRequest) returns (Order);
}
message Order {
string id = 1;
string user_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
int64 total_cents = 5;
google.protobuf.Timestamp created_at = 6;
}
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_CANCELLED = 5;
}
// src/grpc/orderServer.ts
const orderServiceImpl: OrderServiceImplementation = {
async createOrder(call, callback) {
try {
const { userId, items } = call.request.toObject();
const order = await prisma.order.create({
data: { userId, items: { create: items }, status: 'PENDING' },
});
const response = new Order();
response.setId(order.id);
response.setUserId(order.userId);
response.setStatus(OrderStatus.ORDER_STATUS_PENDING);
callback(null, response);
} catch (err) {
callback({ code: Status.INTERNAL, message: 'Internal server error' });
}
},
async getOrder(call, callback) {
const order = await prisma.order.findUnique({ where: { id: call.request.getOrderId() } });
if (!order) return callback({ code: Status.NOT_FOUND, message: `Order not found` });
callback(null, toProtoOrder(order));
},
// Server Streaming: real-time order status updates
watchOrderStatus(call) {
const orderId = call.request.getOrderId();
const subscriber = redis.duplicate();
subscriber.subscribe(`order:${orderId}:status`);
subscriber.on('message', (_ch, message) => {
const update = JSON.parse(message);
const statusUpdate = new OrderStatusUpdate();
statusUpdate.setOrderId(orderId);
statusUpdate.setStatus(update.status);
call.write(statusUpdate);
if (['DELIVERED', 'CANCELLED'].includes(update.status)) { subscriber.unsubscribe(); call.end(); }
});
call.on('cancelled', () => subscriber.unsubscribe());
},
};
Summary
Design gRPC with Claude Code:
- CLAUDE.md — centralize Proto, never change field numbers, use gRPC status codes
- Protobuf — type-safe schema definitions (stronger type system than REST/JSON)
- Server Streaming — real-time status updates (effective WebSocket alternative)
- Metadata — propagate traceId for distributed tracing across microservices
Review gRPC designs with **Code Review Pack (¥980)* using /code-review at prompt-works.jp*
myouga (@myougatheaxo) — Axolotl VTuber.
Top comments (0)