大家好,我是 frank。 欢迎大家点击标题下方蓝色文字「Golang 语言开发栈」关注公众号。 设为星标,第一时间接收推送文章。 文末扫码,大家一起学 Golang 语言。
01
介绍
在之前的几篇文章中,我们介绍了 protobuf 和 grpc,本文我们介绍怎么使用 grpc 开发“分布式系统”。这里使用引号是因为分布式系统是一个大概念,本文我们先介绍使用 grpc 开发分布式系统中的 service。
grpc 是 google 开源的 rpc 框架,使用 grpc 可以方便开发 rpc service;protobuf 是一种接口设计语言(IDL),grpc 框架使用的 IDL 是 protobuf。如果有读者朋友还不了解 protobuf 和 grpc,建议先翻阅之前的几篇文章。
本文是介绍使用 grpc 开发一个实战项目 - ToDoList,目标是帮助读者朋友们熟悉项目开发流程,该实战项目包含 server service 和 client service。server 主要负责数据操作,client 主要负责业务逻辑处理。
02
server
首先,我们创建 proto 目录,并创建 proto 文件,编写 protobuf,设计项目的 service,接着创建 pb 目录,使用 protoc 编译我们编写好的 proto 文件,生成 pb 文件。然后,我们创建 service 目录,编写生成的 pb 文件中接口定义的方法。最后,我们创建 grpc 服务器。
server 目录
.
├── dao
│ ├── mysql.go
│ └── toDoList.go
├── main.go
├── pb
│ ├── todoPb
│ │ ├── toDoList.pb.go
│ │ └── toDoList_grpc.pb.go
│ └── userPb
│ ├── user.pb.go
│ └── user_grpc.pb.go
├── proto
│ ├── toDoList.proto
│ └── user.proto
└── service
└── toDoList.go
编写 proto 文件
读者朋友们如果还不熟悉 protobuf,建议翻阅之前介绍 protobuf 的文章,限于篇幅,本文不再赘述。示例代码如下:
syntax = "proto3";
option go_package = "./todoPb";
service ToDoList {
rpc CreateToDoList (ToDoListDetail) returns (CreateToDoListResult) {}
rpc ReadToDoList (ToDoListPage) returns (ReadToDoListByPage) {}
}
message ToDoListDetail {
// @inject_tag: form:"id" xorm:"'id' not null pk autoincr"
int64 id = 1;
...
完整代码,请查阅 github。
生成 pb 文件
接着,我们使用 protoc 编译 proto 文件,生成 pb 文件,关于怎么使用 protoc 编译 proto 文件,在之前的文章已经详细介绍,限于篇幅,本文不再赘述,编译命令如下:
protoc --go_out=./pb --go-grpc_out=./pb proto/* && protoc-go-inject-tag -XXX_skip=xorm -input=./pb/todoPb/toDoList.pb.go
执行以上命令,将在 pb 目录中自动生成 pb 文件。
编写接口定义的方法
至此,我们开始编写 golang 代码,在 service 目录中创建 go 文件,实现生成的 pb 文件中接口定义的方法。
...
type ToDoList struct {
pb.UnimplementedToDoListServer
}
func (t *ToDoList) CreateToDoList(ctx context.Context, in *pb.ToDoListDetail) (*pb.CreateToDoListResult, error) {
log.Printf("id: %d content:%v datetime:%d\n", in.GetId(), in.GetContent(), in.GetDatetime())
record, err := dao.Add(ctx, in)
data := &pb.CreateToDoListResult{Record: record}
return data, err
}
...
阅读上面这段代码,可以发现我们把数据库操作相关代码设计在 dao 包中。service 中通过调用 dao 包的方法操作数据库,另外,其他数据操作组件也可以在 service 中调用。
完整代码,请查阅 github。
创建 gRPC 服务器
在完成 service 代码编写之后,我们创建 grpc server,然后注册服务。
...
server := grpc.NewServer()
pb.RegisterToDoListServer(server, new(service.ToDoList))
...
完整代码,请查阅 github。
以上就是使用 grpc 创建 rpc service 的一般流程,在生产环境项目中,还需要完善一些公共方法,比如配置文件读取、错误码定义、参数验证等。为了读者朋友们容易理解,该实战项目中未涉及这部分内容,感兴趣的读者朋友们可以尝试自己实现该部分内容。
03
client
client 主要负责业务逻辑,本文介绍的实战项目使用 gin 框架实现路由。通常,client service 的 pb 文件拷贝 server service 生成的 pb 文件。
首先,我们创建 controller 目录,调用 server service 的方法,然后,使用 gin 框架设计路由。
client 目录
.
├── controller
│ └── toDoList.go
├── main.go
├── pb
│ ├── todoPb
│ │ ├── toDoList.pb.go
│ │ └── toDoList_grpc.pb.go
│ └── userPb
│ ├── user.pb.go
│ └── user_grpc.pb.go
└── router
└── router.go
拷贝 server service 生成的 pb 文件
client 直接拷贝 server service 生成的 pb 文件,不需要编写 proto 文件,然后使用 protoc 编译 proto 文件,生成 pb 文件。
编写 controller 代码,调用 server service 的方法
在 controller 目录中创建 go 文件,编写 controller 方法,并创建客户端,使用创建的客户端调用 server service 的方法。
func CreateToDoList(ctx *gin.Context) {
...
cc := NewToDoListClient()
defer func() {
err := cc.Close()
if err != nil {
log.Fatalf("conn close error=%v", err)
}
}()
cli := pb.NewToDoListClient(cc)
ctx1, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := cli.CreateToDoList(ctx1, param)
...
完整代码,请查阅 github。
创建 gin 路由
编写完 controller 之后,创建 router 目录,在 router 目录中创建 gin 路由,用于访问 controller 中的方法。
...
r := gin.Default()
apiV1 := r.Group("/v1")
todolist := apiV1.Group("/todolist")
{
todolist.POST("/add", controller.CreateToDoList)
...
完整代码,请查阅 github。
04
总结
本文我们介绍了怎么使用 grpc 开发 service。读者朋友们阅读完本文,可以了解使用 grpc 开发 service 的一般开发流程,建议感兴趣的读者朋友们,实现项目中 user service 的代码编写。
推荐阅读:
Golang 语言怎么使用 net/http 标准库开发 http 应用?
Golang 语言中 map 的键值类型选择,它是并发安全的吗?
参考资料: https://developers.google.com/protocol-buffers/docs/proto3 https://grpc.io/docs/languages/go/quickstart/ https://gin-gonic.com/ https://gobook.io/read/gitea.com/xorm/manual-zh-CN/