服务(service)是暴露给客户端的远程方法集合。在示例中,ProductInfo 服务有两个远程方法:addProduct(Product) 和getProduct(ProductID)。按照 protocol buffers 的规则,远程方法只能有一个参数,并只能返回一个值。如果需要像 addProduct 方法那样给方法传递多个值,就要定义一个消息类型,并对所有的值进行分组,就像在 Product 消息类型中所做的那样。
addProduct
在系统中创建新的 Product。该方法需要商品的详细信息作为输入,并且如果操作成功完成,就会返回新创建商品的标识符数字。代码清单 2-3 展示了 addProduct 方法的定义。
代码清单 2-3 addProduct 方法的 protocol buffers 定义
rpc addProduct(Product) returns (google.protobuf.StringValue);
getProduct
检索商品信息。该方法需要 ProductID 作为输入,如果系统中存在相应商品,就会返回 Product 的详情。代码清单 2-4 展示了getProduct 方法的定义。
代码清单 2-4 getProduct 方法的 protocol buffers 定义
rpc getProduct(google.protobuf.StringValue) returns (Product);
将消息和服务组合到一起,就有了 ProductInfo 用例的完整 protocolbuffers 定义,如代码清单 2-5 所示。
代码清单 2-5 ProductInfo 服务使用 protocol buffers 的 gRPC 服务定义
syntax = "proto3"; ➊
package ecommerce; ➋
service ProductInfo { ➌
rpc addProduct(Product) returns (ProductID); ➍
rpc getProduct(ProductID) returns (Product); ➎
}
message Product { ➏
string id = 1; ➐
string name = 2;
string description = 3;
}
message ProductID { ➑
string value = 1;
}
❶ 服务定义首先要指定所使用的 protocol buffers 版本(proto3)。
❷ 为了避免协议消息类型之间的命名冲突,这里使用了包名,它也会用于生成代码。
❸ 服务接口的定义。
❹ 用于添加商品的远程方法,它会返回商品 ID 作为响应。
❺ 基于商品 ID 获取商品的远程方法。
❻ Product 消息类型(格式)的定义。
❼ 用来保存商品 ID 的字段(名–值对),使用唯一的数字来标识二进制消息格式中的各个字段。
❽ ProductID 消息类型(格式)的定义。
在 protocol buffers 定义中,可以指定包名(如 ecommerce),这样做能够避免在不同的项目间出现命名冲突。当使用这个包属性生成服务或客户端代码时,除非明确指明了不同的包名,否则将为对应的编程语言生成相同的包。当然,该语言需要支持包的概念,本例代码就是用它来编写的。在定义包名的时候,还可以使用版本号,如 ecommerce.v1 和ecommerce.v2。这样一来,未来对 API 的主要变更就可以在相同的代码库中共存。
对于 IntelliJ IDEA、Eclipse、VSCode 等常用的集成开发环境,现在有支持 protocol buffers 的插件。可以将插件安装到集成开发环境中,这样就能很容易地为服务创建 protocol buffers 定义了。
还有一个过程需要注意,那就是从其他 proto 文件中进行导入。如果需要使用其他 proto 文件中定义的消息类型,那么可以将它们导入本例的protocol buffers 定义中。如果要使用 wrappers.proto 文件中的StringValue 类型(google.protobuf.StringValue),就可以按照如下方式在定义中导入 google/protobuf/wrappers.proto 文件:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package ecommerce;
...
在完成服务定义的规范之后,就可以处理 gRPC 服务和客户端的实现了。