go-zero代码生成器助你高效开发

简介: go-zero代码生成器助你高效开发

Protocol Buffers 是谷歌推出的编码标准,它在传输效率和编解码性能上都要优于 JSON。但其代价则是需要依赖中间描述语言(IDL)来定义数据和服务的结构(通过 *.proto 文件),并且需要一整套的工具链(protoc 及其插件)来生成对应的序列化和反序列化代码。除了谷歌官方提供的工具和插件(比如生成 go 代码的 protoc-gen-go)外,开发者还可以开发或定制自己的插件,根据业务需要按照 proto 文件的定义生成代码或者文档。

goctl rpc 代码生成工具开发的目的:

  1. proto 模版生成
  2. rpc server 代码生成 → 得到的是 go-zero zrpc
  3. 内部包装了 gRPC pb code 的生成
  4. 和 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 生成的核心函数,负责了整个生命周期:

  1. 解析 → proto parse
  2. 模版填充 → proto item into template
  3. 文件生成 → 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

相关文章
|
1月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
191 4
|
3月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
300 0
|
1月前
|
JavaScript 前端开发 Java
【GoWails】Go做桌面应用开发?本篇文章带你上手Wails框架!一步步带你玩明白前后端双端的数据绑定!
wails是一个可以让你使用Go和Web技术编写桌面应用的项目 可以将它看作Go的快并且轻量级的Electron替代品。可以使用Go的功能,并结合现代化UI完成桌面应用程序的开发
321 4
|
2月前
|
监控 前端开发 数据可视化
Github 12.3kstar, 3分钟起步做中后台?Go+Vue 脚手架,把权限、代码生成、RBAC 都封装好了
Go-admin 是基于 Gin + Vue 的中后台脚手架,集成 Casbin RBAC 权限、JWT 鉴权、GORM 数据库操作与 Swagger 文档,内置用户、角色、菜单等管理模块。提供代码生成器与表单构建器,支持多租户与多前端框架(Element UI/Arco/Ant Design),3 分钟快速搭建企业级后台,助力高效交付。
195 4
|
5月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
323 61
|
5月前
|
开发框架 安全 前端开发
Go Web开发框架实践:模板渲染与静态资源服务
Gin 是一个功能强大的 Go Web 框架,不仅适用于构建 API 服务,还支持 HTML 模板渲染和静态资源托管。它可以帮助开发者快速搭建中小型网站,并提供灵活的模板语法、自定义函数、静态文件映射等功能,同时兼容 Go 的 html/template 引擎,具备高效且安全的页面渲染能力。
|
5月前
|
SQL Go 数据库
开箱即用的GO后台管理系统 Kratos Admin - 代码生成工具集
Kratos Admin 是一款开箱即用的 Go 后台管理系统,配套代码生成工具集(cfgexp、sql2orm、sql2proto、sql2kratos),支持配置导出、数据库转 ORM、Protobuf 及 Kratos 微服务代码生成,助力高效开发。
203 1
|
6月前
|
人工智能 缓存 安全
Go开发遇见的一次Data Race
本文通过一段 Go 语言代码示例,分析了并发编程中的数据竞争(Data Race)问题。代码实现了一个带缓存的内存存储系统,包含 `LRUCache` 和 `MemoryCache` 两个核心组件。尽管在 `MemoryCache` 的 `Set` 方法中加了锁保护,但由于直接调用 `LRUCache` 的 `GetLength` 方法时未加锁,导致底层数据结构在多 goroutine 环境下被同时读写,从而触发 Data Race。文章详细解析了问题根源,并提出了解决方案:为 `LRUCache` 的 `Add` 方法添加锁保护,确保并发安全。
|
7月前
|
Go API 定位技术
MCP 实战:用 Go 语言开发一个查询 IP 信息的 MCP 服务器
随着 MCP 的快速普及和广泛应用,MCP 服务器也层出不穷。大多数开发者使用的 MCP 服务器开发库是官方提供的 typescript-sdk,而作为 Go 开发者,我们也可以借助优秀的第三方库去开发 MCP 服务器,例如 ThinkInAIXYZ/go-mcp。 本文将详细介绍如何在 Go 语言中使用 go-mcp 库来开发一个查询 IP 信息的 MCP 服务器。
434 0
|
缓存 弹性计算 API
用 Go 快速开发一个 RESTful API 服务
用 Go 快速开发一个 RESTful API 服务