Protocol Buffers
是谷歌推出的编码标准,它在传输效率和编解码性能上都要优于 JSON。但其代价则是需要依赖中间描述语言(IDL)来定义数据和服务的结构(通过 *.proto
文件),并且需要一整套的工具链(protoc 及其插件)来生成对应的序列化和反序列化代码。除了谷歌官方提供的工具和插件(比如生成 go 代码的 protoc-gen-go)外,开发者还可以开发或定制自己的插件,根据业务需要按照 proto 文件的定义生成代码或者文档。
而 goctl rpc
代码生成工具开发的目的:
- proto 模版生成
rpc server
代码生成 → 得到的是go-zero zrpc
- 内部包装了
gRPC pb code
的生成 - 和 http server 一样,提供了
go-zero
内置的一些管控中间件
我们可以注意到第3点,基本在不使用 codegen tool 情况下,开发者需要自己执行 protoc
+ protoc-gen-go
插件生成对应的 .pb.go
文件。整个过程比较繁琐。
以上是 goctl rpc
的背景。本篇文章先从整体生成的角度阐述 goctl 生成过程,之后再分析一些关键的部分,从而让各位开发者可以开发出契合自己业务系统的 codegen tool。
整体结构
// 推荐使用 v3 版本。现在流行的 gRPC 框架也是使用 v3 版本。
syntax = "proto3";
// 每个 proto 文件需要定义自己的包名,类似 c++ 的名称空间。
package hello;
// 数据结构通过 message 定义
message Echo {
// 每个 message 可以有多个 field。
// 每个 field 需要指定类型、字段名和编号。
// Protocol Buffers 在内部使用编号区分字段,一旦指定就不能更改。
string msg = 1;
}
// 服务使用 servcie 定义
service Demo {
// 每个 service 可以定义多个 rpc
// 每个 rpc 需要指定接口名、传入消息和返回消息三部分。
rpc Echo(Echo) returns (Echo);
}
所谓 代码生成 其实也就是把 proto file(IDL) 的每一部分解析出来,然后再对应每一部分做模版渲染,生成对应的代码即可。
而且在生成过程中,我们还可以借助插件或者定制自己的插件。
我们先看看入口:
{
Name: "protoc",
Usage: "generate grpc code",
UsageText: "example: goctl rpc protoc xx.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.",
Description: "for details, see https://go-zero.dev/cn/goctl-rpc.html",
Action: rpc.ZRPC,
Flags: []cli.Flag{
...
},
}
从 goctl.go(基本上goctl下面的命令入口都在这个文件可以找到)进入:
// ZRPC generates grpc code directly by protoc and generates
// zrpc code by goctl.
func ZRPC(c *cli.Context) error {
...
grpcOutList := c.StringSlice("go-grpc_out")
goOutList := c.StringSlice("go_out")
zrpcOut := c.String("zrpc_out")
style := c.String("style")
home := c.String("home")
remote := c.String("remote")
branch := c.String("branch")
...
goOut := goOutList[len(goOutList)-1]
grpcOut := grpcOutList[len(grpcOutList)-1]
...
var ctx generator.ZRpcContext
...
// 将args中的值逐个赋值给 ZRpcContext,作为env context注入 generator
g, err := generator.NewDefaultRPCGenerator(style, generator.WithZRpcContext(&ctx))
if err != nil {
return err
}
return g.Generate(source, zrpcOut, nil)
}
g.Generate(source, zrpcOut, nil)
→ goctl rpc 生成的核心函数,负责了整个生命周期:
- 解析 → proto parse
- 模版填充 → proto item into template
- 文件生成 → touch generate file
generator
func (g *RPCGenerator) Generate(src, target string, protoImportPath []string, goOptions ...string) error {
...
// proto parser
p := parser.NewDefaultProtoParser()
proto, err := p.Parse(src)
dirCtx, err := mkdir(projectCtx, proto, g.cfg, g.ctx)
// generate Go code
err = g.g.GenEtc(dirCtx, proto, g.cfg)
err = g.g.GenPb(dirCtx, protoImportPath, proto, g.cfg, g.ctx, goOptions...)
err = g.g.GenConfig(dirCtx, proto, g.cfg)
err = g.g.GenSvc(dirCtx, proto, g.cfg)
err = g.g.GenLogic(dirCtx, proto, g.cfg)
err = g.g.GenServer(dirCtx, proto, g.cfg)
err = g.g.GenMain(dirCtx, proto, g.cfg)
err = g.g.GenCall(dirCtx, proto, g.cfg)
...
}
上图展示 Generate() 的代码生成过程。
这里提前说明一些 GenPb()
的过程。为什么要说这个呢?goctl是脱离 protoc 的工具体系,包括和 protoc 插件机制,所以要生成 .pb.go
文件,之间是怎么耦合的呢?
首先查询是否有内置的 xxx 插件,如果没有内置的 xxx 插件那么将继续查询当前系统中是否存在 protoc-gen-xxx
命名的可执行程序,最终通过查询到的插件生成代码。
go-zero
是没有对 protoc
额外编写插件辅助生成代码。所以默认使用的就是 protoc-gen-xxx
生成的go代码。
func (g *DefaultGenerator) GenPb(ctx DirContext,
protoImportPath []string,
proto parser.Proto,
_ *conf.Config,
c *ZRpcContext,
goOptions ...string) error {
...
// protoc 命令string
cw := new(bytes.Buffer)
...
// cw.WriteString("protoc ")
// cw.WriteString(some command shell)
command := cw.String()
g.log.Debug(command)
_, err := execx.Run(command, "")
if err != nil {
if strings.Contains(err.Error(), googleProtocGenGoErr) {
return errors.New(`unsupported plugin protoc-gen-go which installed from the following source:
google.golang.org/protobuf/cmd/protoc-gen-go,
github.com/protocolbuffers/protobuf-go/cmd/protoc-gen-go;
Please replace it by the following command, we recommend to use version before v1.3.5:
go get -u github.com/golang/protobuf/protoc-gen-go`)
}
return err
}
return nil
}
一句话描述 GenPb()
:
根据前面 proto parse 解析出来的结构和路径拼装protoc
编译运行的命令,然后execx.Run(command, "")
直接执行这条命令即可。
所以如果开发者需要加入自己的插件,可以自行修改其中 cw.WriteString(some command shell)
写入自己的执行命令逻辑即可。
总结
以上就是本文的全部内容了。本文从 goctl rpc 生成rpc代码的入口分析了整个生成流程,其中特意提到 .pb.go
文件的生成,开发者可以从此代码部分切入 goctl rpc,加入自己编写的proto插件。当然还有其他部分,会在后续的文章继续分析。
本系列文章的目的:顺便带大家改造一个属于自己的 rpc codegen tool。
项目地址
https://github.com/zeromicro/go-zero
欢迎使用 go-zero
并 star 支持我们!
微信交流群
关注『微服务实践』公众号并点击 交流群 获取社区群二维码。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。