介绍
今天看「The Go Blog」,发现最新的一篇文章是「Go Protobuf: The new Opaque API」。这篇文章介绍了一种生成.pb.go的新API——「Opaque API」,以这种API产生的Go struct,所有字段都是对外隐藏的,.pb.go文件提供一系列方法(get/set/build)来操作这些隐藏的字段。
较于旧API,Go官方称之为「Open Struct API」,也是我们日常使用得最多的API。顾名思义,它产生的Go struct的字段都是公开的。
文章还介绍了「Open Struct API」的弊端和「Opaque API」的好处,但本文仅介绍使用方法,即如何使用「Opaque API」生成新的.pb.go。
准备
- 安装 protoc
brew install protoc@latest
- 安装 protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
- 安装 protoc-gen-go-grpc:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
使用
回顾「Open Struct API」
我的目录结构:
├── client
│ └── main.go
├── pb
├── proto
│ └── test.proto
└── server
└── main.go
该目录下包含grpc服务端和客户端,.proto文件定义在/proto下
🙋♀️🌰: test.proto
syntax = "proto3";
package pb;
option go_package="/pb";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
这个.proto文件通过以下指令编译后,会在/pb下生成.pb.go和_grpc.pb.go:
protoc --go_out=. --go-grpc_out=. proto/test.proto
├── pb
│ ├── test.pb.go
│ └── test_grpc.pb.go
使用「Opaque API」
根据Protocol Buffers的官方文档,使用如下指令可以在不修改.proto文件下生成新的.pb.go
protoc --go_opt=default_api_level=API_HYBRID --go_out=. --go-grpc_out=. proto/test.proto
较于上面的指令,该指令新增了--go_opt=default_api_level=API_HYBRID,指定protoc使用「Opaque API」去编译.proto文件,执行后,会发现在/pb下产生了三个文件:
├── pb
│ ├── test.pb.go
│ ├── test_grpc.pb.go
│ └── test_protoopaque.pb.go
关注test_protoopaque.pb.go,它就是新API的产物!
// ......
// The request message containing the user's name.
type HelloRequest struct {
state protoimpl.MessageState `protogen:"opaque.v1"`
xxx_hidden_Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// ......
func (x *HelloRequest) GetName() string {
if x != nil {
return x.xxx_hidden_Name
}
return ""
}
func (x *HelloRequest) SetName(v string) {
x.xxx_hidden_Name = v
}
type HelloRequest_builder struct {
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
Name string
}
func (b0 HelloRequest_builder) Build() *HelloRequest {
m0 := &HelloRequest{}
b, x := &b0, m0
_, _ = b, x
x.xxx_hidden_Name = b.Name
return m0
}
// ......
可以看到,HelloRequest struct的Name => xxx_hidden_Name! Name字段被私有化了,下面则是对Name的一系列操作方法。Get/Set没什么好说的,关注下Build:
// 以前的方式
obj := &pb.HelloRequest{Name: "Anson"}
// 现在的方式
obj := pb.HelloRequest_builder{Name: "Anson"}.Build()
细心的朋友可以发现了test.pb.go文件,test.pb.go跟「Open Struct API」产生的.pb.go文件名一样,但内容可就不一样了,Go对这个文件做了向后兼容,有了它,项目可以兼容旧代码,你只需要写新代码时注意不要.公开字段就好,所有对struct的操作都用方法的形式。
而且test.pb.go顶部有一个编译标志://go:build !protoopaque
而test_protoopaque.pb.go 顶部也有一个编译标志://go:build protoopaque,这是一对相反的编译标志。意味着,当你go run或者go build时,如果指定-tags protoopaque,则编译器会编译test_protoopaque.pb.go,反之编译test.pb.go。如果是新项目,就可以编译时指定一下编译标志,如果是旧项目,就别指定编译标志了,在写新代码时注意一下就行。
迁移
手动迁移
如果message的数量少可以选择Manual Migration,即手动改代码,所有操作message对象的地方都要换成新方式。
自动迁移
但如果是企业级项目,最好选择 Automated Migration,使用这个包:
google.golang.org/open2opaque,但这种方式我还没试过,大家有兴趣可以试一下
总结
这篇文章属于操作指南,介绍了如何使用新的「Opaque API」生成新的.pb.go和如何使用新的struct。至于Go为什么要推出新API,大家有兴趣就去仔细看看Blog的下半部分,大致是因为私有化字段能减少内存使用和提高运行效率,并且可能是无法忍受公开化字段埋下的坑点了。
参考: