RPC(远程过程调用)是计算机上的通信协议,可用于将编程中的函数与其他语言和其他位置或分布式系统中的计算机连接起来。
发送的数据格式通常使用JSON(JSON-RPC),或者也可以是XML(XML-RPC),使用XML格式的协议的示例是SOAP(简单对象访问协议)。在本文中,我们将使用gRPC(gRPC远程过程调用),其数据交换格式使用Protobuf。它也可以是JSON(通过使用grpc-gateway)。gRPC是谷歌于2016年8月发布的开源项目。
想象一下下面的图片,几个服务链接在一起支持数字业务需求:
RPC的微服务架构
当三个服务(auth,invoice,payment)相互分离时,所获得的好处是当一个服务无法运行时,我们将立即修复有问题的服务。调试和错误修复将更容易,因为单独的每个服务中的每个代码都没有合并到一个代码中。
例如,当付款服务有问题时,公司将替换付款网关。然后,其他服务将不受影响,因为我们已经拥有了适合服务之间的数据交换接口格式(消息)。每个服务都是独立的,但服务组被链接以支持业务操作。
然后在图片中,有一个网关来访问服务;在技术术语中,它更常被称为API网关。它的工作是管理客户端需要的内部服务的路由,类似于反向代理,其功能也是公开从计算机网络外部访问的内部服务。
如果你作为开发人员仍然对概念感到困惑,请尝试查看以下图片:
在不同语言中的问候函数调用
例如,我有一个使用Golang语言编写的Greet()
函数或方法,它简单而强大。然后使用Typescript语言的客户端需要自动访问我们使用Golang编写的源代码的过程。
为了进行请求/响应,我们使用gRPC,其数据交换格式使用Protobuf,以便可以在客户端-服务器之间进行连接。通过gRPC和Protobuf,我们将获得_protoc_编译器创建的源代码存根和模拟。
为了更容易理解gRPC,我确保你已经了解了静态类型的语言,因为它将有助于我们的学习过程。其主要特点是类型安全,因此错误将由编译器处理,而不是在运行时程序运行时处理。
还有一些用于运行gRPC服务操作的组件,如下所示:
服务定义
首先,使用Protobuf提供的IDL(接口定义语言)定义服务将如何操作。通常,你需要编写:服务的名称,请求/响应数据模型的格式,如何通信以及一些其他配置,例如导入包和命名目标语言中将使用的包。
编写完所有消息组件后,你将使用_protoc_将.proto文件编译为目标编程语言。但是,本文将间接使用 protoc 从.proto构建生成的文件。
通过Buf.build,我们只需提供YAML文件中的配置即可。它会自动生成_protoc_命令,以使工作更快,更高效。
消息类型(异步/同步)
gRPC中的异步通信模型具有流数据一词,而同步通信模型具有一元调用。基本上,异步进程不会阻塞主线程以继续以下进程;例如,在Golang中,有“goroutine”的概念,在Javascript中,有“Promise”的概念。
此外,gRPC中的客户端-服务器通信可以使用单向或同时(双向)。在gRPC通信模型中,还有类似于HTTP的标准状态代码;例如,对于gRPC中的200 OK状态代码,它将是0 OK;有关更多详细信息,你可以在这里查看。
我们将构建一个名为Student Service的服务,用于存储大学生的数据。作者将选择Typescript作为gRPC中的客户端,并在Golang中编写服务业务逻辑。但在实践中,当实现gRPC服务时,你可以自由使用任何语言,只要gRPC支持它。让我们设计服务系统的工作方式。
本节将重点介绍在.proto文件中编写客户端-服务器通信模型接口,其中包含要在学生服务中实现的定义和通信方案。在进一步进行之前,请准备以下一些先决条件工具,以开始遵循本文:
- Go v1.19.x
- Javascript: v18.14.x和Typescript v4.9.x
- Protoc: protoc-gen-go v1.30.0和protoc-gen-go-grpc 1.3.0
- Buf Build: v1.15.1
- Git: v2.38.1
首先,你必须安装Golang;有关更多详细信息,请在这里阅读并使用v1.19.x,以便在运行源代码时不会出现错误。如果你已成功安装Golang,请运行go version
命令以检查安装过程。之后,请运行以下命令使用Go Mod安装_protoc_和_buf_工具。
# Installing Buf CLI for .proto builders
go install github.com/bufbuild/buf/cmd/buf@1.15.1
# Installing the grpc utility tool
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
go install github.com/fullstorydev/grpcui/cmd/grpcui@latest
# Installing the protoc build tool for golang
go install google.golang.org/protobuf/cmd/protoc-gen-go@1.30.0
go install google.golang.org/protobuf/cmd/protoc-gen-go-grpc@1.3.0
为了更轻松地安装Node.js,你可以使用NVM(Node Version Manager);请在这里阅读。然后,你需要使用npm全局安装Typescript编译器;请使用以下命令:
npm install --global typescript@v4.9.x ts-node
然后运行tsc --version
命令以确保已全局安装Typescript编译器。
注意:你可以在我的 GitHub 代码库中完整查看所有源代码:https://github.com/khafidprayoga/student-svc。在本文中,请跟随流程;如果有不清楚的地方,请将该代码库克隆为指南。
本节将重点介绍在 .proto 文件中定义消息格式。在编写服务之前,请在 Golang 上初始化一个新模块,并创建以下文件夹:
proto/
├── buf.yaml
└── student
└── v2
└── student.proto
2 directories, 2 files
# create a new working directory
mkdir student-svc
# move directory
cd student-svc
# init go module
go mod init github.com/khafidprayoga/student-svc
# create a working directory for service proto
mkdir -p proto/student/v2
以上目录结构具有以下格式:proto/{service-name}/{proto-message-version}。这不是 Protobuf 的标准,而是一个样式指南,以便在将来更容易实现相互连接的服务,如上面的第一张图片(服务 Auth、Payment、Invoice)。然后,请使用以下命令在 student proto 中创建一个文件:touch proto/student/v2/student.proto
。成功创建文件后,请复制下面的 .proto 代码:
syntax = "proto3";
package student.v2;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/khafidprayoga/student-svc/v2;studentv2";
enum StudentNationality{
STUDENT_NATIONALITY_UNSPECIFIED = 0;
STUDENT_NATIONALITY_CITIZEN = 1;
STUDENT_NATIONALITY_FOREIGN = 2;
}
enum GenderType {
GENDER_TYPE_UNSPECIFIED = 0;
GENDER_TYPE_MALE = 1;
GENDER_TYPE_FEMALE = 2;
GENDER_TYPE_OTHER = 3;
}
message CreateStudentRequest{
string full_name = 2;
google.protobuf.Timestamp birth_date = 3;
GenderType gender = 4;
string address = 5;
repeated string hobbies = 6;
StudentNationality nationality = 7;
string email = 8;
}
message CreateStudentResponse{
string id = 1;
string full_name = 2;
google.protobuf.Timestamp birth_date = 3;
int32 gender = 4;
string address = 5;
repeated string hobbies = 6;
int32 nationality = 7;
string email = 8;
}
message GetDetailStudentRequest {
string student_id = 1;
}
message GetDetailStudentResponse{
string id = 1;
string full_name = 2;
google.protobuf.Timestamp birth_date = 3;
int32 gender = 4;
string address = 5;
repeated string hobbies = 6;
int32 nationality = 7;
string email = 8;
google.protobuf.Timestamp created_at = 9;
google.protobuf.Timestamp updated_at = 10;
}
message GetListStudentRequest{}
message GetListStudentResponse{
repeated GetDetailStudentResponse details = 1;
}
message DeleteStudentRequest {
string student_id = 1;
}
message DeleteStudentResponse{
string message = 1;
}
service StudentService{
rpc CreateStudent(CreateStudentRequest) returns (CreateStudentResponse);
rpc GetDetailStudent(GetDetailStudentRequest) returns (GetDetailStudentResponse);
rpc DeleteStudent(DeleteStudentRequest) returns (DeleteStudentResponse);
rpc GetListStudent(GetListStudentRequest) returns (GetListStudentResponse);
}
代码包含以下内容:
- 在代码中,我们使用更安全的 proto 版本 3 定义消息格式。
- Proto 文件的包名称为
student.v2
。 - 导入来自 Google 的 proto 文件的依赖项,
Timestamp.proto
,它将用于将 Golang 包中的time.Time
格式化为 Google 的标准时间戳格式。 - 从构建语言目标 Golang 中的选项中,包含我们之前初始化的模块名称;以及稍后将生成的包的名称
_studentv2_
。 - 枚举包含
Gender
和Nationality
常量。 - 请求/响应消息用于稍后在客户端和服务器之间进行数据交换的格式。
- 最后是我们的服务名称和客户端将使用的方法。
为了更好地理解 Protocol Buffers 的语法,请访问官方文档:
语言指南(proto 3):https://protobuf.dev/programming-guides/proto3/
接下来,请使用以下命令创建 buf.yaml
文件:touch proto/buf.yaml
。请复制以下代码并保存。
version: v1
name: buf.build/khafidprayoga/student-svc
deps:
- buf.build/googleapis/googleapis
breaking:
use:
- FILE
lint:
use:
- DEFAULT
代码包含以下内容:
version
是 Buf.Build 系统的构建接口版本。name
可以稍后用于将 proto 生成的包发布到称为 Buf Schema Registry 的远程存储库;这个概念类似于 Docker Hub。deps
是 proto 文件的依赖项;例如,在 proto 文件中,有导入 Google 的timestamp.proto
包的代码。breaking
用于检查破坏已经在使用的 API 的更改。例如,在CreateStudent
请求中,有一个带有字符串类型的full_name
变量,它使用标签号码 2。有一天,你突然使用标签号码 2 并将其与另一个属性一起使用,例如first_name
。lint
包含 buf linter 配置,它将检查我们的 proto 代码。例如,你的变量命名f
将被指示更改为 snake-case。
buf.yaml 文件定义了一个模块,并放置在其定义的 Protobuf 源文件的根目录中。buf.yaml 配置的放置告诉 buf 在哪里搜索 .proto 文件,并如何处理导入。
此文件包含 lint 和 breaking change 检测规则,以及(如果适用)你的模块名称和依赖项列表。 https://buf.build/docs/configuration/v1/buf-yaml
最后,请转到根工作目录 student-svc/
,然后使用以下命令创建 buf.gen.yaml
文件:touch buf.gen.yaml
,并复制以下代码:
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/khafidprayoga/student-svc/gen
except:
- buf.build/googleapis/googleapis
plugins:
- name: go
out: gen
opt: paths=source_relative
- plugin: buf.build/grpc/go
out: gen
opt:
- paths=source_relative
- plugin: grpc-gateway
out: gen
opt:
- paths=source_relative
- generate_unbound_methods=true
- plugin: buf.build/community/timostamm-protobuf-ts
out: client/gen
buf.gen.yaml
是一个构建配置文件,用于生成客户端服务器存根和模拟。
buf.gen.yaml 文件定义了本地插件模板,并由 buf generate 命令用于生成所选语言的代码。
一切准备就绪后,当我们开始与 Buf 的构建脚本进行交互时,请转到工作空间目录 student-svc
。然后运行以下命令 buf generate proto/
,其意图是在 proto/The directory
中构建所有 .proto
文件,将其转换为 Golang 和 Typescript 语言的客户端/服务器模拟和存根接口;结果如下:
students.proto 文件的构建结果
构建结果将存储在以下目录中:
gen/student/v2
用于 Golang 语言的接口client/gen
用于 Typescript 语言的接口
通过 Buf 生成的 protoc 生成的文件后,你可以直接安装客户端-服务器在操作 gRPC 服务时所需的依赖项或库。
请在工作目录中运行 go mod tidy
命令,以安装 Golang 依赖关系图中缺少的导入库。完成后,它将更新包含服务器所需库列表的 go.mod
文件。将会有一个包含通过 https://pkg.go.dev/ 下载的软件包的校验和的 go.sum
文件。```
如果出现以下错误:
github.com/khafidprayoga/student-svc imports
github.com/khafidprayoga/student-svc/common/config: no matching versions for query "latest"
github.com/khafidprayoga/student-svc imports
github.com/khafidprayoga/student-svc/common/constant: no matching versions for query "latest"
github.com/khafidprayoga/student-svc imports
github.com/khafidprayoga/student-svc/common/data: no matching versions for query "latest"
github.com/khafidprayoga/student-svc imports
github.com/khafidprayoga/student-svc/svc: no matching versions for query "latest
因为你本地计算机上的模块包在目录中找不到,Go 模块将尝试通过主机名 github.com
进行搜索。现在,请在我的 Github 存储库 https://github.com/khafidprayoga/student-svc 上查看实现。
对于 Typescript 中的客户端文件夹,请使用 tsc — init
命令初始化新模块以生成 tsconfig.js 文件,并运行 npm install 命令安装客户端需要的库;它将生成 package.json 和 package-lock.json 文件。
tsconfig.json 配置文件中的目标 JavaScript 版本输出是 ES2020,因为有一个名为 BigNumber 的特性,如果版本低于此目标,它将无法运行,这个特性将用于与 Google 的 proto 时间戳库进行交互。
此外,Buf 可以使客户端和服务器之间的接口明确、可访问和吸引人。该工具还有一个 linter,如果我们编写的 .proto 文件不符合它们的标准或样式指南,则可以检测出错误。
现在尝试将 CreateStudentRequest 方法中的 full_name
字段更改为 fullName
。然后运行 buf lint proto/ 命令,会出现以下警告:
proto/student/v2/student.proto:22:10:Field name "fullName" should be lower_snake_case, such as "full_name".
最后但并非最不重要的一个有趣的特性是:
Breaking Changes Detection
Buf 可以检测我们编写的消息格式中的破坏性更改。例如,将属性更改为 first_name
,而标签 2 已分配给 full_name
属性,则会出现以下警告:
<input>:1:1:Previously present file "proto/student/v2/student.proto" was deleted.
proto/student/v2/student.proto:22:3:Field "2" with name "first_name" on message "CreateStudentRequest" changed option "json_name" from "fullName" to "firstName".
proto/student/v2/student.proto:22:10:Field "2" on message "CreateStudentRequest" changed name from "full_name" to "first_name".
为了创建警告和错误,我运行了以下命令:
buf breaking proto/ --against=".git#branch=master"
该命令旨在检查当前分支是否会破坏现有分支 master
上的消息格式。如果你的服务已经公开并且有很多人使用它,这非常有用,解决方案是创建一个新版本。
我直接将 student-svc 版本设置为版本 2,因为在版本 1 中,我使用了可以在一个服务器上以双栈运行 gRPC 和 REST 的协议 https://connect.build 进行了实现。对于这个协议的实现版本,我将其保存在 connect-go-examples 分支中。
Buf Schema Registry
远程存储库可以减小构建构件的大小;实质上,gen/
和 client/genLocal
文件夹将被发布到 Buf Registry 存储库中,.proto 文件将自动出现在那里;有趣的是,你还可以使用 markdown 文件编写你的服务文档。
Buf Schema Registry (BSR)
我认为到这里就够了,因为这篇文章太长了;如果你对更现代的 Buf 构建系统感兴趣,请自行探索,并别忘了尝试使用 Connect 协议。对于 gRPC 服务器实现,我使用了一个带有 map[string]any 的内存数据库,在实现的客户端侧,我执行了 RPC 调用方法 GetStudentDetail。
在初始数据中,有一个 ID 为“seed”的学生;当服务器启动时,初始用户会自动插入内存中。
参考:
译自:https://khafidprayoga.medium.com/building-modern-grpc-service-with-buf-build-5c1b5c0bb4e6
评论(0)