Protobuf 传统工作流的痛点:protoc 命令行地狱
Protocol Buffers 作为 Google 推出的跨语言数据序列化方案,已经成为微服务通信、配置管理和数据存储的事实标准。然而,传统的 Protobuf 工作流长期以来被 protoc(Protocol Buffer Compiler)的复杂命令行所困扰,这种困扰在团队规模扩大和项目复杂度增长时会成倍放大。
protoc 的第一个痛点是插件管理的混乱。要为 Go 生成代码,你需要安装 protoc-gen-go;要生成 gRPC 服务端和客户端代码,需要安装 protoc-gen-go-grpc;如果要生成网关代码,还需要 protoc-gen-grpc-gateway;验证插件是 protoc-gen-validate。每个插件都有自己的版本,版本之间经常不兼容。团队中的每个开发者都需要安装完全相同的插件版本组合,这在大型团队中是一个噩梦——新成员入职时需要花数小时配置环境,CI 镜像中需要维护复杂的安装脚本。
第二个痛点是 protoc 命令行本身的长度与复杂度。一个典型的代码生成命令可能长这样:
protoc \
--proto_path=./proto \
--proto_path=./third_party \
--go_out=paths=source_relative:./gen/go \
--go-grpc_out=paths=source_relative:./gen/go \
--grpc-gateway_out=paths=source_relative:./gen/go \
--validate_out=lang=go,paths=source_relative:./gen/go \
./proto/**/*.proto
这还只是单语言生成的情况。如果项目需要同时生成 Go、TypeScript、Python、Java 客户端,命令行的复杂度会进一步爆炸。更重要的是,这个命令写在 Makefile 或脚本中后,任何 proto 文件的增减或路径变更都需要同步修改这些构建脚本,维护成本非常高。
第三个痛点是缺乏标准化的 Schema 管理。在分布式团队中,proto 文件散落在各个仓库中,有些团队直接复制 proto 文件而不是使用版本管理。当一个服务的 proto 定义发生变化时,依赖该服务的消费者往往无法及时感知,直到编译或运行时才发现不兼容。protoc 本身不提供任何有关代码风格检查、向后兼容性验证或格式化规范的工具,这些都需要团队自行建立规范并通过人工 code review 来检查,既低效又不可靠。
第四个痛点是跨仓库依赖管理。在微服务架构中,一个 proto package 经常需要 import 另一个仓库中的 proto 定义。传统的做法是通过 git submodule 或直接复制文件来管理这些外部依赖,但两种方式都有严重缺陷。git submodule 的更新和版本锁定十分繁琐,而直接复制则会导致版本不同步且历史追溯困难。
Buf 正是为了解决上述所有痛点而诞生的。它将 protoc 的底层编译能力与现代化的构建系统理念相结合,提供了一套声明式的配置驱动工作流,彻底改变了 Protobuf 的开发体验。
Buf 的设计理念与核心组件
Buf 公司(由前 Google 工程师创建)将 Buf 定位为一个 Protobuf 构建系统和 Schema 管理平台。它的设计理念可以总结为:用声明式配置替代命令行参数、用标准化规则替代人工检查、用集中式注册表替代分散的文件复制。
Buf 的核心组件包括 CLI 工具 buf、配置文件系统(buf.yaml 和 buf.gen.yaml)、Lint 和 Breaking Change 检测引擎、格式化工具(buf format)以及 Buf Schema Registry(BSR)。这些组件共同构成了一条完整的 Protobuf 生命周期管理链:编写 proto 文件 → 格式化检查 → Lint 规范检查 → Breaking Change 兼容性检测 → 代码生成 → 发布到注册表 → 消费者引用。
与 protoc 不同,Buf CLI 不直接编译 proto 文件。相反,它使用自己的内部解析器和构建引擎来处理 proto 文件,这个引擎基于 Google 的开源 protobuf 解析库但经过了重写优化。这意味着 Buf 可以在不安装任何 protoc 插件的情况下完成语法检查、Lint、兼容性分析等工作。只有在执行代码生成时,Buf 才会作为 protoc 的封装器调用底层的插件系统。
Buf 的另一个重大改进是引入了模块(Module)的概念。一个 Buf 模块由一个 buf.yaml 文件和其所在的目录下的 proto 文件组成。模块有唯一的名称和版本,可以被其他模块依赖。这种模块化的设计使得 proto Schema 的管理方式与 npm、Go modules、Maven 等现代包管理器一致,开发者可以通过简单的声明来管理跨仓库依赖。
buf.yaml 配置详解:name、deps、lint、breaking
buf.yaml 是 Buf 模块的核心配置文件,类似于 Go 的 go.mod 或 Node.js 的 package.json。它定义了模块的身份、依赖、Lint 规则和兼容性检查规则。
一个典型的 buf.yaml 如下:
version: v1
name: buf.build/acme/payments
breaking:
use:
- FILE
lint:
use:
- DEFAULT
enum_zero_value_suffix: _UNSPECIFIED
rpc_allow_same_request_response: false
service_suffix: API
deps:
- buf.build/acme/commonapis
- buf.build/googleapis/googleapis
version 字段指定配置文件格式的版本,当前主流是 v1。name 字段定义了模块的全局唯一标识,格式为 buf.build/{owner}/{repository}。如果模块计划发布到 BSR,name 是必需的。
deps 字段声明了本模块依赖的其他 Buf 模块。每个依赖项是一个 BSR 地址,Buf 在执行构建时会自动从 BSR 拉取这些依赖的 proto 文件(使用 buf export 或 buf generate 时)。依赖版本通过 buf.lock 文件锁定,类似于 go.sum。开发者可以使用 buf update 命令来更新依赖版本。
breaking 字段配置了向后兼容性检查的规则。use: [FILE] 表示使用文件级别的兼容性检查。可选级别包括 FILE、 WIRE、WIRE_JSON 和 PACKAGE。FILE 是最严格的级别,要求任何修改都不能影响 .proto 文件的语义结构;WIRE 仅保证二进制序列化格式的兼容性;WIRE_JSON 还额外检查 JSON 表示的兼容性;PACKAGE 则在包级别进行检查。
lint 字段配置了代码风格检查规则。use: [DEFAULT] 表示使用 Buf 的默认规则集,它覆盖了 Google、Uber 等公司的 Protobuf 最佳实践。可选的规则集还包括 BASIC(最基础规则)、MINIMAL(最小规则集)以及社区维护的规则集。lint 下还可以配置具体规则的开关和参数,例如 enum_zero_value_suffix 要求枚举的零值必须带 _UNSPECIFIED 后缀,service_suffix 要求 gRPC 服务名必须以特定后缀结尾。
以下是针对不同项目类型的 buf.yaml 示例对比:
# 内部微服务项目的 buf.yaml(严格模式)
version: v1
name: buf.build/mycompany/orderservice
breaking:
use:
- FILE
lint:
use:
- DEFAULT
except:
- PACKAGE_VERSION_SUFFIX
rpc_allow_google_protobuf_empty_requests: true
rpc_allow_google_protobuf_empty_responses: true
deps:
- buf.build/mycompany/common:v1.2.0
# 对外 SDK 项目的 buf.yaml(兼容优先)
version: v1
name: buf.build/mycompany/publicapi
breaking:
use:
- WIRE_JSON
lint:
use:
- MINIMAL
buf.gen.yaml 代码生成配置
buf.gen.yaml 是 Buf 的代码生成配置文件,它彻底改变了 protoc 命令行的使用方式。不再需要手写冗长的 protoc 命令,所有生成器(managed plugins)和输出路径都可以在配置文件中声明。
一个典型的 buf.gen.yaml 如下:
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/acme/payments/gen/proto/go
except:
- buf.build/googleapis/googleapis
override:
buf.build/acme/commonapis: github.com/acme/commonapis/gen/proto/go
plugins:
- plugin: go
out: gen/proto/go
opt:
- paths=source_relative
- plugin: go-grpc
out: gen/proto/go
opt:
- paths=source_relative
- plugin: grpc-gateway
out: gen/proto/go
opt:
- paths=source_relative
- generate_unbound_methods=true
version 字段同样指定文件格式的版本。managed 部分启用了 Buf 的 managed mode,这是 Buf 最强大的功能之一。Managed mode 会自动管理 proto 文件中的 option go_package、option java_package、option csharp_namespace 等语言特定选项。开发者不再需要手动在每个 proto 文件中添加这些 boilerplate 选项,Buf 会根据生成配置自动注入。go_package_prefix 定义了默认的 Go import 路径前缀,except 排除了某些模块,override 允许为特定模块指定不同的前缀。
plugins 数组定义了要使用的所有代码生成插件。plugin 字段是插件名称(对应的二进制必须存在于 PATH 中)。out 是输出目录。opt 是传递给插件的选项。对比等效的 protoc 命令,buf.gen.yaml 的声明式格式显然更易于阅读和维护。
当运行 buf generate 时,Buf 会读取 buf.gen.yaml 和当前模块的 proto 文件,按配置自动调用对应的 protoc 插件。由于 Buf 内部已经完成了 proto 文件的解析和依赖管理,buf generate 不需要手动指定 -I 或 --proto_path——所有导入路径都在 buf.yaml 的 deps 中隐式管理。
如果需要生成多种语言的代码,只需在 plugins 中添加更多插件声明:
plugins:
# Go
- plugin: go
out: gen/proto/go
opt: paths=source_relative
- plugin: go-grpc
out: gen/proto/go
opt: paths=source_relative
# TypeScript
- plugin: buf.build/bufbuild/es
out: gen/proto/ts
opt: target=ts
# Python
- plugin: buf.build/community/googleapis-cn-python
out: gen/proto/python
从 Buf 1.9 开始,Buf 还支持 Remote Plugins,可以直接引用 BSR 上托管的插件版本,而不需要在本地安装插件。这使得 CI 环境中不再需要维护复杂的插件安装步骤。
buf lint:Proto 规范检查与自定义规则
buf lint 是 Buf 提供的代码风格检查工具,它基于可配置的规则集自动检测 proto 文件中的风格问题。与手动 code review 相比,lint 工具的优势在于一致性和自动化——所有团队成员遵循相同的标准,且每次提交都可以自动验证。
运行 lint 很简单:
# 检查当前目录下的 proto 模块
buf lint
# 检查指定目录
buf lint proto/
# 检查特定文件
buf lint proto/acme/payments/v1/payment.proto
Buf 内置了多个规则集,从最宽松的 MINIMAL 到最严格的 DEFAULT。以下是 DEFAULT 规则集涵盖的部分规则示例:
ENUM_VALUE_PREFIX:枚举值的名称必须包含枚举类型名作为前缀ENUM_ZERO_VALUE_SUFFIX:枚举的零值必须以_UNSPECIFIED结尾FIELD_LOWER_SNAKE_CASE:字段名必须使用小写蛇形命名MESSAGE_PASCAL_CASE:消息名必须使用 PascalCasePACKAGE_LOWER_SNAKE_CASE:包名必须使用小写蛇形命名PACKAGE_VERSION_SUFFIX:包名必须包含版本后缀(如v1)RPC_REQUEST_RESPONSE_UNIQUE:RPC 的请求和响应消息不能相同RPC_REQUEST_STANDARD_NAME:请求消息名应为 RPC 名 +RequestSERVICE_SUFFIX:服务名必须以指定后缀结尾(默认是Service,可配置)SYNTAX_SPECIFIED:proto 文件必须显式声明syntax = "proto3";
当 lint 发现违规时,会输出类似这样的信息:
proto/acme/payments/v1/payment.proto:12:3:Field name "userID" must be lower_snake_case.
proto/acme/payments/v1/payment.proto:5:1:Package name "acme.payments" must have a version suffix (e.g., "acme.payments.v1").
proto/acme/payments/v1/payment.proto:8:1:Enum value name "PENDING" must be prefixed with "STATUS_".
在 CI 中使用 lint 的脚本:
# .github/workflows/proto-lint.yaml
name: Proto Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
version: 'latest'
- run: buf lint
除了内置规则,Buf 还支持通过 buf.yaml 的 lint 部分进行规则级别的自定义。可以排除某些规则、修改规则参数或仅启用特定规则。对于有特殊需求的团队,还可以编写自定义 lint 插件来实现组织特定的规范检查。
buf breaking:向后兼容性检测
在微服务架构中,proto Schema 的向后兼容性是最重要的契约之一。一个服务端在不通知客户端的情况下修改了 proto 定义,可能导致生产环境大规模故障。buf breaking 就是专门用来防止这种灾难的工具。
buf breaking 通过比较当前 proto 版本与某个基准版本(base version),检查所有变更是否违反了兼容性承诺。它支持多种基准来源:本地文件路径、Git 分支或标签、以及 BSR 上的历史版本。
# 与 Git 的 main 分支比较
buf breaking --against '.git#branch=main'
# 与上一个 Git tag 比较
buf breaking --against '.git#tag=v1.0.0'
# 与本地目录比较
buf breaking --against ../baseline
# 与 BSR 上的历史版本比较
buf breaking --against buf.build/acme/common:v1.0.0
buf breaking 的核心是检查规则集。在 buf.yaml 中通过 breaking.use 配置使用哪种级别的检查:
FILE:最严格的语义兼容性。任何可能改变 proto 文件含义的操作都被禁止,包括删除字段、修改字段编号、修改字段类型、删除消息等。PACKAGE:包级别的兼容性。允许在包内部进行不影响导出的修改。WIRE:二进制有线格式兼容性。目标是确保旧客户端可以正确解析新服务端发送的消息。允许添加新字段、删除 optional 字段等,但不允许修改字段编号或类型。WIRE_JSON:二进制和 JSON 格式兼容性。额外要求 JSON 字段名保持不变。
对于面向公众的 API(如对外 SDK),建议使用 WIRE_JSON 以最大化兼容性。对于内部微服务,团队的灵活性可能更重要,FILE 级别的严格检查适合在团队准备好协调所有消费者的迁移时使用。
buf breaking 的输出非常清晰:
proto/acme/payments/v1/payment.proto:15:3:Field "3" on message "Payment" changed type from "int64" to "string".
proto/acme/payments/v1/payment.proto:8:1:Previously present message "LegacyPayment" was deleted.
将 buf breaking 集成到 CI 中可以确保任何向后不兼容的变更都无法合并到主分支:
# .github/workflows/proto-breaking.yaml
name: Proto Breaking Change Check
on: [pull_request]
jobs:
breaking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 需要完整 git 历史
- uses: bufbuild/buf-action@v1
- run: buf breaking --against '.git#branch=origin/main'
Buf Schema Registry(BSR):中心化管理
Buf Schema Registry(BSR)是 Buf 公司提供的托管服务,类似于 npm registry、Go proxy 或 Maven Central,但专门为 Protobuf Schema 设计。BSR 允许团队将 proto 模块发布到中心化注册表,其他团队可以通过简单的声明来消费这些模块,而无需关心 proto 文件的具体存放位置。
将模块发布到 BSR:
# 登录 BSR
buf login
# 创建仓库(首次)
buf beta registry repository create buf.build/acme/payments --visibility=public
# 推送当前模块
buf push
推送成功后,BSR 会自动为该模块生成文档网站、提供可浏览的依赖图、并触发自定义 webhook(如通知下游消费者)。每个推送的版本都有唯一的 commit digest,类似于 git commit hash,确保内容不可篡改。
消费 BSR 模块的方式极其简单。在 buf.yaml 的 deps 中声明:
deps:
- buf.build/acme/payments:v1.2.0
然后运行:
buf generate
Buf 会自动从 BSR 拉取指定版本的 proto 文件,与本地 proto 一起进行代码生成。无需 git submodule、无需文件复制、无需手动同步。BSR 会为拉取的依赖缓存到本地(~/.cache/buf 或项目目录的 .buf),后续构建无需重复下载。
BSR 的另一个重要特性是 Generated SDK。对于支持的模块,BSR 可以直接生成并托管多语言的 SDK,消费者可以直接用对应语言的包管理器引用,而无需本地的 buf generate。例如一个 TypeScript 前端项目可以直接从 BSR 获取 npm 包:
npm install @buf/acme_payments.bufbuild_es
Generated SDK 将代码生成从生产方转移到了消费方,而且由 BSR 保证 SDK 始终保持最新。这是 Protobuf 生态中的重大革新。
与 Go gRPC 的集成:从 proto 到 Go 代码
Go 是 gRPC 和 Protobuf 生态中最重要的语言之一。Buf 与 Go gRPC 的集成非常流畅,通过 buf.gen.yaml 配置 go 和 go-grpc 插件即可自动生成 Go 结构体和 gRPC 服务接口。
# buf.gen.yaml
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/acme/payments/gen/proto/go
plugins:
- plugin: go
out: gen/proto/go
opt: paths=source_relative
- plugin: go-grpc
out: gen/proto/go
opt:
- paths=source_relative
- require_unimplemented_servers=false
在这个配置下,buf generate 会输出两类文件:_pb.go(包含消息结构体和序列化方法)和 _grpc.pb.go(包含客户端接口、服务端接口和注册函数)。paths=source_relative 确保生成的代码保持与 proto 文件相同的目录结构。require_unimplemented_servers=false 是 go-grpc 插件的选项,控制生成服务端接口时是否嵌入 UnimplementedXxxServer 结构体。
生成的服务端实现示例如下:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "github.com/acme/payments/gen/proto/go/acme/payments/v1"
)
type server struct {
pb.UnimplementedPaymentServiceServer
}
func (s *server) CreatePayment(ctx context.Context, req *pb.CreatePaymentRequest) (*pb.CreatePaymentResponse, error) {
return &pb.CreatePaymentResponse{
Payment: &pb.Payment{
Id: "pay-123",
Amount: req.Amount,
Status: pb.PaymentStatus_PAYMENT_STATUS_PENDING,
},
}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterPaymentServiceServer(s, &server{})
log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
如果使用 Buf 的 Managed mode,go_package 选项会自动注入到 proto 文件中,开发者只需关注业务逻辑而无需维护语言特定的选项。
对于 Go 模块版本管理,可以将生成的代码作为独立模块发布,或者直接在服务仓库中维护。推荐的做法是将 proto 定义放在独立的 schema 仓库中,生成代码后发布到专用仓库或直接由 BSR 托管 Generated SDK,服务代码通过 go modules 引用生成的包。
多语言代码生成统一管理
现代微服务生态中,一个后端 API 通常需要同时暴露给 Go、TypeScript、Python、Java、Kotlin 等多语言客户端。Buf 通过统一的 buf.gen.yaml 配置使得多语言代码生成变得集中和标准化。
以下是一个完整的多语言生成配置:
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/acme/payments/gen/proto/go
plugins:
# Go
- plugin: go
out: gen/proto/go
opt: paths=source_relative
- plugin: go-grpc
out: gen/proto/go
opt: paths=source_relative
# TypeScript (connect-es or protobuf-es)
- plugin: buf.build/bufbuild/es
out: gen/proto/ts
opt: target=ts
# Python
- plugin: buf.build/community/googleapis-cn-python
out: gen/proto/python
# Java
- plugin: java
out: gen/proto/java
# Kotlin
- plugin: kotlin
out: gen/proto/kotlin
# Swift
- plugin: swift
out: gen/proto/swift
opt: Visibility=Public
通过 Remote Plugins 功能,上述配置无需在本地安装任何插件——Buf 会在执行时自动从 BSR 下载所需插件。这不仅简化了 CI 配置,还保证了团队中每个成员使用的插件版本完全一致。
多语言生成的另一个关键是类型映射的一致性。Buf 的 Managed mode 会自动为支持的每种语言注入对应的包名选项(java_package、csharp_namespace、swift_prefix 等),开发者可以在 buf.gen.yaml 中为每种语言配置前缀规则。
与 CI/CD 集成:PR 级兼容性检查
将 Buf 完全集成到 CI/CD 流水线中,可以实现 proto Schema 变更的自动化治理。推荐的流水线如下:
- Lint:在 PR 中自动检查是否符合团队的命名和风格规范
- Breaking Change:在 PR 中检测是否违反了向后兼容性
- Generate & Test:生成代码并运行单元测试验证生成代码的可用性
- Push to BSR:合并到主分支后自动发布新版本
# .github/workflows/proto.yaml
name: Proto CI
on:
push:
branches: [main]
paths: ['proto/**', 'buf.yaml', 'buf.gen.yaml']
pull_request:
paths: ['proto/**', 'buf.yaml', 'buf.gen.yaml']
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
lint: true
breaking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: bufbuild/buf-action@v1
with:
breaking: true
against: 'https://github.com/${{ github.repository }}.git#branch=main'
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
- run: buf generate
- run: go test ./gen/...
push:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [lint, breaking, generate]
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
push: true
token: ${{ secrets.BUF_TOKEN }}
这个流水线保证了对 proto 的任何修改都必须通过三项检查才能合并,合并后自动推送到 BSR 供下游消费。这为规模化的微服务团队提供了 Schema 治理的自动化基础。
从 protoc 迁移到 buf 的完整步骤
对于正在使用 protoc 的团队,迁移到 Buf 是一个渐进而平滑的过程。以下是推荐的迁移步骤。
步骤 1:安装 Buf CLI
# macOS/Linux
brew install bufbuild/buf/buf
# 或者下载二进制
curl -sSL "https://github.com/bufbuild/buf/releases/download/v1.28.0/buf-$(uname -s)-$(uname -m)" -o /usr/local/bin/buf
chmod +x /usr/local/bin/buf
步骤 2:创建 buf.yaml
在项目根目录创建 buf.yaml,将当前 proto 文件组织为一个 Buf 模块。如果有外部依赖,在 deps 中声明对应 BSR 模块。
cd my-project
buf config init
buf config init 会引导式生成初始配置。然后手动调整 name、deps 和规则集。
步骤 3:验证现有 proto 文件
buf lint
首次运行 lint 可能会发现大量现有问题(因为团队之前可能没有任何规范)。不要试图一次性修复所有问题——可以渐进式启用规则,或先用 except 排除特别棘手的规则,逐步收紧。
步骤 4:创建 buf.gen.yaml
将现有的 protoc 命令转换为 buf.gen.yaml 格式。参考现有命令中的 -I 路径和 --*_out 参数来映射插件和输出路径。
步骤 5:验证代码生成
buf generate
对比 buf generate 的输出与之前 protoc 的输出,确保生成的文件结构一致。由于 Buf 使用 managed mode,go_package 等选项可能被自动注入,需要确认生成的 Go 代码 import 路径是否正确。
步骤 6:更新 CI/CD
将 protoc 和相关插件的安装步骤替换为 Buf CLI 安装。将构建命令替换为 buf generate。添加 buf lint 和 buf breaking 检查。
步骤 7:发布到 BSR(可选但推荐)
注册 BSR 账号,创建组织,获取 token。在 CI 中添加 buf push 步骤。更新依赖方从 git 引用或文件复制改为 buf.build 引用。
完整实战:构建一个企业级 Buf 工作流
下面展示一个企业级的 Buf 工作流配置,涵盖 proto 组织、CI/CD、多语言生成和 BSR 发布。假设公司名称为 TechCorp,拥有订单、支付和用户三个核心领域。
目录结构:
techcorp-apis/
├─ buf.yaml # 根模块配置
├─ buf.gen.yaml # 代码生成配置
├─ buf.lock # 锁定文件
├─ proto/
│ └─ techcorp/
│ ├─ common/v1/
│ │ └─ types.proto
│ ├─ order/v1/
│ │ └─ order_service.proto
│ ├─ payment/v1/
│ │ └─ payment_service.proto
│ └─ user/v1/
│ └─ user_service.proto
├─ gen/ # 生成代码(不纳入版本控制或单独仓库)
└─ .github/workflows/
└─ proto.yaml
buf.yaml:
version: v1
name: buf.build/techcorp/apis
breaking:
use:
- FILE
lint:
use:
- DEFAULT
except:
- PACKAGE_VERSION_SUFFIX
deps:
- buf.build/googleapis/googleapis
buf.gen.yaml:
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/techcorp/apis/gen/proto/go
plugins:
- plugin: go
out: gen/proto/go
opt: paths=source_relative
- plugin: go-grpc
out: gen/proto/go
opt: paths=source_relative
- plugin: buf.build/bufbuild/es
out: gen/proto/ts
opt: target=ts
GitHub Actions 流水线(完整版):
name: Proto Pipeline
on:
push:
branches: [main]
paths:
- 'proto/**'
- 'buf.yaml'
- 'buf.gen.yaml'
- 'buf.lock'
pull_request:
paths:
- 'proto/**'
- 'buf.yaml'
- 'buf.gen.yaml'
- 'buf.lock'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
version: 'latest'
- run: buf lint
breaking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: bufbuild/buf-action@v1
with:
version: 'latest'
- run: buf breaking --against '.git#branch=main,subdir=proto'
generate-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21'
- uses: bufbuild/buf-action@v1
with:
version: 'latest'
- run: buf generate
- run: go test ./gen/...
- name: Check generated code is up-to-date
run: |
git diff --exit-code gen/ || (echo "Generated code is out of date. Run 'buf generate' locally." && exit 1)
push-bsr:
runs-on: ubuntu-latest
needs: [lint, breaking, generate-and-test]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
version: 'latest'
token: ${{ secrets.BUF_TOKEN }}
push: true
proto 文件示例:
// proto/techcorp/order/v1/order_service.proto
syntax = "proto3";
package techcorp.order.v1;
import "techcorp/common/v1/types.proto";
option csharp_namespace = "Techcorp.Order.V1";
option java_multiple_files = true;
option java_package = "com.techcorp.order.v1";
option php_namespace = "Techcorp\\Order\\V1";
// OrderService 处理订单生命周期管理
service OrderService {
// CreateOrder 创建新订单
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
// GetOrder 获取订单详情
rpc GetOrder(GetOrderRequest) returns (Order);
// ListOrders 列出用户的订单
rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
}
message Order {
string id = 1;
string user_id = 2;
common.v1.Money total = 3;
OrderStatus status = 4;
string created_at = 5;
}
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_DELIVERED = 4;
ORDER_STATUS_CANCELLED = 5;
}
message CreateOrderRequest {
string user_id = 1;
repeated OrderLineItem items = 2;
}
message OrderLineItem {
string product_id = 1;
int32 quantity = 2;
common.v1.Money unit_price = 3;
}
message CreateOrderResponse {
Order order = 1;
}
message GetOrderRequest {
string order_id = 1;
}
message ListOrdersRequest {
string user_id = 1;
int32 page_size = 2;
string page_token = 3;
}
message ListOrdersResponse {
repeated Order orders = 1;
string next_page_token = 2;
}
在这个企业级工作流中,每次对 proto 文件的变更都会触发 Lint、Breaking Change 检查和代码生成验证。只有通过所有检查且生成代码是最新的,变更才能合并。合并后 BSR 会自动得到更新版本,下游服务可以立即感知并拉取最新定义。
总结
Buf 为 Protobuf 和 gRPC 工作流带来了现代化的工程体验。它将分散的命令行工具、手动管理的依赖和脆弱的文件复制,替换为了声明式配置、自动化检查和中心化注册表。对于使用 Protobuf 的 Go 团队来说,迁移到 Buf 意味着更简化的开发环境、更严格的质量门禁和更流畅的跨团队协作。
核心收益包括:通过 buf.yaml 和 buf.gen.yaml 将 protoc 的复杂性封装为可维护的配置;通过 buf lint 和 buf breaking 在 CI 中自动执行 Schema 治理;通过 BSR 实现 proto 模块的版本化管理和跨团队共享;通过 Remote Plugins 消除本地插件管理负担。随着 BSR Generated SDK 的推出,消费方甚至可以跳过代码生成步骤,直接从注册表获取多语言 SDK,这是 Protobuf 生态的重大进化。
对于正在使用 Protobuf 或计划采用 gRPC 的 Go 项目,从今天开始使用 Buf 是一个回报极高的决策。从最简单的 buf lint 开始,逐步引入 buf breaking 和 BSR,团队将在数月内建立起一套可持续演进的 Schema 管理体系。
常见问题与进阶技巧
Q: Buf 会替代 protoc 吗?
A: Buf CLI 在内部解析 proto 文件时不依赖 protoc,但在执行代码生成时仍会调用 protoc 插件。对于日常开发,开发者几乎不需要直接使用 protoc。Buf 的设计哲学是封装 protoc 的底层细节而非完全替代它。
Q: 是否有 Buf 的本地替代方案,不需要使用 BSR 云服务?
A: Buf CLI 的所有核心功能(lint、breaking、generate、format)都是完全本地运行的,不需要 BSR。BSR 只在需要跨团队协作和模块共享时才必要。对于完全内网的场景,可以使用 buf export 手动管理依赖,或使用 Buf 的企业版私有注册表。
Q: 如何处理 proto 文件中的敏感信息?
A: Protobuf Schema 本身不应包含敏感信息(如密码、密钥)。如果 proto 注释中包含内部信息,注意 BSR 公共仓库会暴露这些内容。可以启用 BSR 的私有仓库选项,或者在 CI 中过滤敏感注释。
Q: Buf 支持 proto2 语法吗?
A: Buf CLI 完全支持 proto2 和 proto3 的解析与检查。但一些 Lint 规则和 Managed mode 的选项仅适用于 proto3。如果项目仍在使用 proto2,建议逐步迁移到 proto3 以利用 Buf 的全部功能。
Q: 版本锁定文件 buf.lock 应该纳入版本控制吗?
A: 强烈推荐纳入版本控制,类似于 go.sum 或 package-lock.json。它确保了所有开发者和 CI 使用完全相同的依赖版本。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。