Table of contents
Open Table of contents
TL;DR
Buf 用一个 CLI 统一了 protobuf 的构建、lint、breaking change 检测、代码生成、格式化和依赖管理。它替代的不是 protobuf 本身,而是 protoc + 手动管理插件 + Makefile/shell 脚本的传统工作流。核心价值:buf.yaml 定义模块和规则,buf.gen.yaml 定义代码生成,buf generate 一个命令完成所有事。
Buf 解决的问题:protoc 的痛点
传统 protoc 工作流的典型状态:
# Makefile 里的 protoc 调用 — 项目越大越失控
generate:
protoc \
-I ./proto \
-I ./third_party/googleapis \ # 手动下载的依赖
-I $(GOPATH)/src/github.com/grpc-ecosystem/grpc-gateway \ # 路径可能不对
--go_out=./gen --go_opt=paths=source_relative \
--go-grpc_out=./gen --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=./gen --grpc-gateway_opt=paths=source_relative \
--openapiv2_out=./openapi \
./proto/**/*.proto
| protoc 的痛点 | Buf 怎么解决 |
|---|---|
| 插件要手动安装、版本不一致 | remote plugin 从 BSR 拉取,版本锁定 |
| 依赖(googleapis 等)要手动下载 | deps 字段声明,buf dep update 拉取 |
| import 路径容易出错 | 模块系统自动解析 |
| 没有 lint 规则 | 内置 lint,检查命名规范、包结构等 |
| breaking change 靠人眼审 | buf breaking 自动检测 |
| Makefile 越写越长 | buf generate 一个命令 |
| CI 里要装 protoc + 所有插件 | CI 里只装 buf |
安装
# macOS
brew install bufbuild/buf/buf
# Linux
curl -sSL https://github.com/bufbuild/buf/releases/latest/download/buf-Linux-x86_64 \
-o /usr/local/bin/buf && chmod +x /usr/local/bin/buf
# 验证
buf --version
核心配置文件
Buf 的一切行为由两个 YAML 文件控制。
buf.yaml — 模块定义 + lint 规则 + breaking 策略
version: v2
modules:
- path: proto # proto 文件根目录
name: buf.build/yourorg/im # BSR 上的模块名(可选,发布时需要)
deps:
- buf.build/googleapis/googleapis # google.api.http 注解
- buf.build/bufbuild/protovalidate # 验证规则
lint:
use:
- STANDARD # 标准 lint 规则集
breaking:
use:
- FILE # 最严格的 breaking change 检测
# 拉取依赖(类似 go mod tidy)
buf dep update
buf.gen.yaml — 代码生成配置
version: v2
clean: true # 生成前清空 output 目录
managed:
enabled: true # 自动管理 go_package 等 option
override:
- file_option: go_package_prefix
value: github.com/yourorg/im-server/gen
plugins:
# Go message/enum 类型
- remote: buf.build/protocolbuffers/go
out: gen/go
opt: paths=source_relative
# Go gRPC server/client
- remote: buf.build/grpc/go
out: gen/go
opt: paths=source_relative
# grpc-gateway HTTP 反向代理
- remote: buf.build/grpc-ecosystem/gateway
out: gen/go
opt: paths=source_relative
# grpc-gateway OpenAPI spec
- remote: buf.build/grpc-ecosystem/openapiv2
out: openapi
# TypeScript(前端 proto 类型)
- remote: buf.build/bufbuild/es
out: gen/ts
opt: target=ts
inputs:
- directory: proto
# 一个命令生成所有代码
buf generate
日常工作流
代码生成
# 改了 proto 文件后,一个命令全搞定
buf generate
# 生成的文件结构
gen/
├── go/im/user/v1/
│ ├── user.pb.go # message 类型
│ ├── user_grpc.pb.go # gRPC server/client interface
│ └── user.pb.gw.go # grpc-gateway HTTP 映射
├── go/im/message/v1/
│ └── ...
└── ts/im/user/v1/
└── user_pb.ts # TypeScript 类型
Lint — 强制命名规范
buf lint
# 典型输出:
# proto/user.proto:15:3: Field name "userName" should be lower_snake_case.
# proto/user.proto:1:1: Package name "User" should be lower_snake_case.
STANDARD 规则集包含的核心检查:
| 规则 | 检查内容 |
|---|---|
FIELD_LOWER_SNAKE_CASE | 字段名必须 snake_case |
SERVICE_SUFFIX | Service 名必须以 Service 结尾 |
RPC_REQUEST_RESPONSE_UNIQUE | 每个 RPC 的 Request/Response 不能复用 |
PACKAGE_DIRECTORY_MATCH | 包名和目录结构一致 |
ENUM_ZERO_VALUE_SUFFIX | enum 零值必须以 _UNSPECIFIED 结尾 |
COMMENT_SERVICE / COMMENT_RPC | service 和 rpc 必须有注释 |
Breaking Change 检测
# 对比当前代码和 main 分支,检测 breaking change
buf breaking --against '.git#branch=main'
# 典型输出:
# proto/user.proto:32:3: Field "1" on message "RegisterRequest" changed type from "string" to "int64".
# proto/user.proto:1:1: Previously present service "UserService" was deleted.
检测级别:
| 级别 | 检测内容 | 适用场景 |
|---|---|---|
FILE | 字段类型/编号、service/rpc 删除、文件删除等所有变更 | 推荐默认 |
PACKAGE | 同上但允许跨文件移动定义 | 重组 proto 文件结构时 |
WIRE_JSON | 只检查影响序列化兼容性的变更 | 第三方/公开 API |
WIRE | 只检查二进制序列化兼容性 | 最宽松 |
格式化
# 格式化所有 proto 文件(类似 gofmt)
buf format -w
Remote Plugin vs Local Plugin
# Remote plugin — 从 BSR 拉取,不需要本地安装
plugins:
- remote: buf.build/protocolbuffers/go
out: gen/go
opt: paths=source_relative
# Local plugin — 使用本地安装的 protoc-gen-xxx
plugins:
- local: protoc-gen-go
out: gen/go
opt: paths=source_relative
| 方式 | 优点 | 缺点 |
|---|---|---|
| remote | 不需要安装插件,版本锁定,CI 友好 | 首次拉取需要网络 |
| local | 离线可用,可用自定义插件 | 要手动安装和管理版本 |
推荐用 remote,除非有离线需求或用了不在 BSR 上的自定义插件。
Managed Mode — 自动管理 option
Proto 文件里的 option go_package 是重复的样板:
// ❌ 每个 proto 文件都要写,改包路径要改所有文件
option go_package = "github.com/yourorg/im-server/gen/im/user/v1;userv1";
Managed mode 自动生成这些 option,proto 文件里不用写:
# buf.gen.yaml
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/yourorg/im-server/gen
// ✅ proto 文件干净了
syntax = "proto3";
package im.user.v1;
// 不需要 option go_package,buf 自动算出来
依赖管理
# buf.yaml
deps:
- buf.build/googleapis/googleapis # google.api.http
- buf.build/bufbuild/protovalidate # buf.validate
- buf.build/envoyproxy/protoc-gen-validate # pgv(如果还在用)
# 拉取/更新依赖
buf dep update
# 生成 buf.lock(类似 go.sum,锁定依赖版本)
依赖自动解析 import:
// 不需要手动下载 googleapis 到 third_party/
// buf 自动从 BSR 解析
import "google/api/annotations.proto";
import "buf/validate/validate.proto";
CI/CD 集成
# GitHub Actions
- name: Install Buf
uses: bufbuild/buf-action@v1
- name: Lint
run: buf lint
- name: Breaking change detection
run: buf breaking --against 'https://github.com/yourorg/im-server.git#branch=main'
- name: Generate
run: buf generate
- name: Check generated code is committed
run: |
git diff --exit-code gen/ || (echo "Generated code not committed" && exit 1)
关键:buf breaking 放在 CI 里,PR 改了 proto 如果有 breaking change 会自动拦截。 不靠人眼审,靠工具保证。
IM 项目的完整配置示例
目录结构
im-server/
├── proto/ # proto 源文件
│ └── im/
│ ├── user/v1/
│ │ └── user.proto
│ ├── message/v1/
│ │ └── message.proto
│ └── ws/v1/
│ └── ws.proto
├── gen/ # 生成代码(提交到 git)
│ ├── go/im/...
│ └── ts/im/...
├── openapi/ # 生成的 OpenAPI spec
├── buf.yaml
├── buf.gen.yaml
└── buf.lock # 自动生成,锁定依赖版本
buf.yaml
version: v2
modules:
- path: proto
deps:
- buf.build/googleapis/googleapis
- buf.build/bufbuild/protovalidate
lint:
use:
- STANDARD
breaking:
use:
- FILE
buf.gen.yaml
version: v2
clean: true
managed:
enabled: true
override:
- file_option: go_package_prefix
value: github.com/yourorg/im-server/gen
plugins:
- remote: buf.build/protocolbuffers/go
out: gen/go
opt: paths=source_relative
- remote: buf.build/grpc/go
out: gen/go
opt: paths=source_relative
- remote: buf.build/grpc-ecosystem/gateway
out: gen/go
opt: paths=source_relative
- remote: buf.build/grpc-ecosystem/openapiv2
out: openapi
- remote: buf.build/bufbuild/es
out: gen/ts
opt: target=ts
inputs:
- directory: proto
日常命令
buf dep update # 更新依赖
buf lint # 检查 proto 规范
buf format -w # 格式化
buf generate # 生成所有代码
buf breaking --against '.git#branch=main' # PR 前检查 breaking change
Buf vs protoc 总结
| 维度 | protoc | Buf |
|---|---|---|
| 插件管理 | 手动安装 protoc-gen-xxx | remote plugin 自动拉取 |
| 依赖管理 | 手动下载到 third_party/ | deps 声明 + buf dep update |
| Lint | 无 | 内置 STANDARD/MINIMAL 规则集 |
| Breaking change | 无 | buf breaking 自动检测 |
| 格式化 | 无(有第三方 clang-format) | buf format 内置 |
| 配置方式 | Makefile + shell 脚本 | buf.yaml + buf.gen.yaml |
| CI 友好度 | 要装 protoc + 所有插件 | 只装 buf |
| 学习曲线 | 低(但维护成本高) | 中(但一次配置长期受益) |
Pitfalls
clean: true会删除 output 目录下的所有文件。如果你在gen/目录下手写了代码(不应该),会被清掉。生成目录里只放生成代码- remote plugin 首次运行需要网络。中国网络环境下可能超时,必要时换成 local plugin 并在 CI 里缓存插件二进制
buf breaking对比的是 proto 文件,不是生成代码。改了 proto 但忘了buf generate,breaking 检测通过但生成代码过时。CI 里应该加git diff --exit-code gen/检查- managed mode 的
go_package_prefix和 proto 里手写的option go_package冲突。用了 managed mode 就不要在 proto 里写option go_package,否则行为不可预测 buf lint的STANDARD规则集可能比你预期的严格。比如要求每个 service 和 rpc 都有注释、enum 零值必须叫XXX_UNSPECIFIED。初期不想全部遵守可以用except排除特定规则,但长期建议全部满足