Use Cases (更新: 2026/6/3)

用 Claude Code 开发 gRPC:Protobuf、流式传输与运维

用 Claude Code 构建可运行的 gRPC 服务,涵盖 Protobuf、期限、流式、认证和监控。

用 Claude Code 开发 gRPC:Protobuf、流式传输与运维

先写清契约,再让 Claude Code 写实现

gRPC 是一种远程过程调用框架。客户端像调用本地函数一样调用另一个服务的方法,但真正的通信发生在网络上。Protocol Buffers 负责定义契约:服务、方法、请求、响应和字段编号。Claude Code 可以很快写出服务器和客户端,但如果契约不清楚,后面的生成代码、错误处理、测试和文档都会一起偏掉。

工作时请以官方资料为准:入门看 gRPC IntroductionCore concepts,Node 实现看 gRPC Node basics,期限看 Deadlines,错误看 Status Codes,认证看 Authentication,监控看 OpenTelemetry Metrics,字段演进看 Protocol Buffers proto3 guide

相关的站内内容可以继续读:生产级 API 开发Claude Code 微服务设计API 版本策略测试策略

适合 gRPC 的实际场景

不要只因为“性能好”就选择 gRPC。更好的判断是:这个通信是否需要强契约、明确的失败语义和跨语言一致性。

使用场景为什么适合 gRPC交给 Claude Code 的任务
订单、库存、账单等内部服务类型化契约能减少团队间的接口漂移编写 .proto、服务器、客户端和状态码表
大量导出或同步数据服务端流式传输可以分批返回设计 returns (stream Item) 并处理取消
Go、Node、Python 混合团队同一个契约可以生成多语言客户端整理生成命令、目录结构和 CI 检查
内部工具或 AI 代理明确方法比自由文本调用更可控加示例客户端、认证元数据、期限和日志

Masa 在小型服务里的经验是,过早做一个万能的 Search 方法会让后续演进变难。读、写、流式导出最好分开,让 Claude Code 先审查 .proto,再改实现。

可直接运行的 Node gRPC 示例

这个示例使用 @grpc/proto-loader 动态读取 .proto,第一次本地验证不需要安装 protoc

mkdir claude-grpc-demo
cd claude-grpc-demo
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
mkdir proto

package.json:

{
  "type": "commonjs",
  "scripts": {
    "server": "node server.js",
    "client": "node client.js"
  },
  "dependencies": {
    "@grpc/grpc-js": "latest",
    "@grpc/proto-loader": "latest"
  }
}

proto/task.proto:

syntax = "proto3";

package tasks.v1;

service TaskService {
  rpc CreateTask(CreateTaskRequest) returns (Task);
  rpc GetTask(GetTaskRequest) returns (Task);
  rpc ListTasks(ListTasksRequest) returns (stream Task);
}

message Task {
  string id = 1;
  string title = 2;
  string status = 3;
  int64 created_at_unix = 4;
}

message CreateTaskRequest {
  string title = 1;
}

message GetTaskRequest {
  string id = 1;
}

message ListTasksRequest {
  int32 limit = 1;
}

server.js:

const path = require("node:path");
const { randomUUID } = require("node:crypto");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const PROTO_PATH = path.join(__dirname, "proto", "task.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: false,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const taskProto = grpc.loadPackageDefinition(packageDefinition).tasks.v1;
const token = process.env.DEMO_TOKEN || "dev-token";
const tasks = new Map();

function grpcError(code, message) {
  const error = new Error(message);
  error.code = code;
  return error;
}

function assertAuthenticated(call) {
  const value = call.metadata.get("authorization")[0];
  if (value !== `Bearer ${token}`) {
    throw grpcError(grpc.status.UNAUTHENTICATED, "UNAUTHENTICATED");
  }
}

function createTask(call, callback) {
  try {
    assertAuthenticated(call);
    const title = String(call.request.title || "").trim();
    if (!title) {
      return callback(grpcError(grpc.status.INVALID_ARGUMENT, "INVALID_ARGUMENT: title"));
    }

    const task = {
      id: randomUUID(),
      title,
      status: "OPEN",
      createdAtUnix: String(Math.floor(Date.now() / 1000)),
    };
    tasks.set(task.id, task);
    callback(null, task);
  } catch (error) {
    callback(error);
  }
}

function getTask(call, callback) {
  try {
    assertAuthenticated(call);
    const task = tasks.get(call.request.id);
    if (!task) {
      return callback(grpcError(grpc.status.NOT_FOUND, "NOT_FOUND: task"));
    }
    callback(null, task);
  } catch (error) {
    callback(error);
  }
}

function listTasks(call) {
  try {
    assertAuthenticated(call);
    const limit = Math.min(Math.max(Number(call.request.limit) || 10, 1), 100);
    for (const task of Array.from(tasks.values()).slice(0, limit)) {
      call.write(task);
    }
    call.end();
  } catch (error) {
    call.destroy(error);
  }
}

const server = new grpc.Server();
server.addService(taskProto.TaskService.service, { createTask, getTask, listTasks });
server.bindAsync("127.0.0.1:50051", grpc.ServerCredentials.createInsecure(), (error, port) => {
  if (error) throw error;
  console.log(`TaskService listening on ${port}`);
});

client.js:

const path = require("node:path");
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

const PROTO_PATH = path.join(__dirname, "proto", "task.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: false,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});
const taskProto = grpc.loadPackageDefinition(packageDefinition).tasks.v1;
const client = new taskProto.TaskService("127.0.0.1:50051", grpc.credentials.createInsecure());
const metadata = new grpc.Metadata();
metadata.set("authorization", `Bearer ${process.env.DEMO_TOKEN || "dev-token"}`);

function deadline(ms) {
  return new Date(Date.now() + ms);
}

function createTask(title) {
  return new Promise((resolve, reject) => {
    client.createTask({ title }, metadata, { deadline: deadline(1000) }, (error, task) => {
      if (error) return reject(error);
      resolve(task);
    });
  });
}

function getTask(id) {
  return new Promise((resolve, reject) => {
    client.getTask({ id }, metadata, { deadline: deadline(1000) }, (error, task) => {
      if (error) return reject(error);
      resolve(task);
    });
  });
}

function listTasks(limit) {
  return new Promise((resolve, reject) => {
    const rows = [];
    const stream = client.listTasks({ limit }, metadata, { deadline: deadline(1000) });
    stream.on("data", (task) => rows.push(task));
    stream.on("error", reject);
    stream.on("end", () => resolve(rows));
  });
}

async function main() {
  const created = await createTask("Claude Code gRPC");
  const fetched = await getTask(created.id);
  const rows = await listTasks(10);
  console.log(JSON.stringify({ created, fetched, streamed: rows.length }, null, 2));
  client.close();
}

main().catch((error) => {
  console.error(error.code, error.details || error.message);
  client.close();
  process.exitCode = 1;
});

一个终端启动服务器:

npm run server

另一个终端运行客户端:

npm run client

架构演进与常见失败

Protocol Buffers 的重点是字段编号。已经发布的编号不要改,也不要复用。删除字段时,用 reserved 保留编号和名称;新增字段时使用新编号;如果要区分“未设置”和“空值”,考虑 optional

message Task {
  string id = 1;
  string title = 2;
  string status = 3;
  int64 created_at_unix = 4;
  optional string assignee_email = 5;
  reserved 6, 7;
  reserved "owner_email";
}

期限也必须明确。官方文档说明,如果客户端不设置期限,调用可能一直等待。示例里的 1 秒只适合本地验证,生产环境要根据延迟、处理时间和重试策略调整。

流式传输适合导出和进度推送,但不要先把所有数据放进内存再写出。要处理客户端取消、网络中断和部分失败。认证方面,示例中的 createInsecure() 只能用于本地。生产环境应使用 TLS:服务端换成 grpc.ServerCredentials.createSsl(...),客户端换成 grpc.credentials.createSsl(rootCert),再通过元数据传令牌。

监控要能回答“哪个方法、哪个状态、耗时多少、是否超时、是否取消”。OpenTelemetry 的 gRPC 指标可以作为基线。错误码不要全部写成 UNKNOWN,而要区分 INVALID_ARGUMENTNOT_FOUNDUNAUTHENTICATEDDEADLINE_EXCEEDEDUNAVAILABLERESOURCE_EXHAUSTED。写入类 RPC 只有在有幂等键时才适合自动重试。

给 Claude Code 的提示词

实现 gRPC TaskService。
要求:
- 先创建 proto/task.proto
- 使用 Node.js、@grpc/grpc-js、@grpc/proto-loader
- 实现 CreateTask、GetTask、ListTasks 服务端流式传输
- 每个客户端 RPC 都要设置 deadline
- 校验 authorization metadata
- 说明生产环境要把 createInsecure 换成 TLS
- 运行 npm run client 并报告输出
可编辑文件:
- proto/task.proto
- server.js
- client.js
- package.json

审查时再发:

请按 findings first 审查这个 gRPC 实现。
重点检查字段编号复用、schema evolution、deadline、cancellation、
status code、streaming 内存行为、TLS/auth、observability。
按严重程度排序,并给出具体文件位置。

变现导线与验证结果

个人学习可以从免费 Claude Code 速查表开始,把这个示例改成一次性练习项目。团队如果需要 .proto 管理、CI 关卡、审查提示词、TLS 上线和监控设计,可以查看 Claude Code 培训与咨询。需要可复用资料时,再看 ClaudeCodeLab 产品

本次改写中,示例已在临时目录用 Node v24.14.1 和 npm 11.11.0 实际运行:npm installnode server.jsnode client.js 都通过,客户端输出包含 createdfetchedstreamed: 1。这台机器没有 goprotoc 和 Go 的 Protobuf 插件,所以本文选择了可验证的 Node 动态加载路径。

#Claude Code #gRPC #Protocol Buffers #microservices #backend
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。