DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Designing gRPC with Claude Code: Proto Definitions, Type-Safe Service Communication, Streaming

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
// 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());
  },
};
Enter fullscreen mode Exit fullscreen mode

Summary

Design gRPC with Claude Code:

  1. CLAUDE.md — centralize Proto, never change field numbers, use gRPC status codes
  2. Protobuf — type-safe schema definitions (stronger type system than REST/JSON)
  3. Server Streaming — real-time status updates (effective WebSocket alternative)
  4. 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)