Protocol hub

随着RPC服务在微服务中使用,大量服务接口需要进行管理,便于不同服务进行对接。那接口应该如何管理呢?

0x01 存放位置划分

每个服务仓库自己管理

在阅读哔哩哔哩被迫开源的主站代码可以看出,B站采用的单仓库管理模型,所以服务接口定义的protocol文件不需要集中放到一个资源中心(仓库/目录),每个服务的目录在api文件夹放入接口描述文件proto,然后生成的目标语言文件也放在该目录,当其他服务依赖该服务的时候,直接import即可。大概模型如下:

|--repo
   |--main
     	|--srv-a
      |  	|--api
      |   |	 |--srv-a.proto
      |   |  |--srv-a.go
      |   |--internal
      |--srv-b
         |--api
         |  |--srv-b.proto
         |  |--srv-b.go
         |--internal

集中管理,protocol hub

即是将所有的协议文件放到个集中的位置(hub),服务根据各种命名空间规则,分属到不同的文件目录下。划分一般有如下维度:服务层次(接入层服务,领域层服务,基础设施层服务),产品线,中台业务归属划分等。网络上公开的可以参考Googleapis仓库的管理,协议集中管理,依赖服务根据自身情况import使用。Googleapis提供协议的同时也提供根据proto 生成的目标语言仓库可以选择依赖。例如go语言genproto仓库, java语言仓库 当然我们也可以proto去编译生成自己的仓库中,目录结构下文会提到。

0x02 提供原始proto文件还是目标语言文件

只提供原始proto文件

服务方提供接口定义的原始proto文件,依赖方根据自身的需要编译成目标语言的桩代码,这时一般需要一个仓库集中管理proto文件。

只提供目标语言的桩代码

服务方预先编译技术栈中使用的目标语言桩代码,并放到公共的仓库中,依赖的服务import目标语言的仓库即可,这个时候需要一个公共的桩代码仓库来归档管理桩代码。

原始文件和目标语言桩代码都提供

像Googleapis 一样,接口的定义文件proto 和目标语言生成的桩代码都提供,依赖的服务方根据自己的需要选择自己生成桩代码还是依赖已经生成的桩代码都行。

0x03 proto目录风格

上文说了proto放置位置的管理,接下来讨论下目录风格。proto定义了接口,涉及到版本,命令定义,资源定义,错误码定义;

服务版本:api的版本,例如v1,v2

命令定义:request 和response的定义

资源定义:DTO对象,例如 message User{}

错误码定义: 业务的状态码的定义

e.g:

syntax = "proto3";

//包定义
package google.ads.googleads.v1.services;
//rpc服务定义
service AdGroupAdService {
  option (google.api.default_host) = "googleads.googleapis.com";
	//rpc 接口定义
  rpc GetAdGroupAd(GetAdGroupAdRequest) returns (AdGroupAd) {
    option (google.api.http) = {
      get: "/v1/{resource_name=customers/*/adGroupAds/*}"
    };
    option (google.api.method_signature) = "resource_name";
  }
}
//请求接口参数定义
message GetAdGroupAdRequest{}
//资源定义
message AdGroupAd{}
//ErrCode 定义
enum ErrAdGropAdService{
	ok = 0;
}

上面的示例是拷贝的Googleapis中的proto定义做示例说明,包定义中声明了接口的版本rpc接口定义和请求参数归为指令 AdGroupAd归为资源 ErrAdGroupAdService 归为错误码(错误,业务码)。下面描述下对proto中几种不同类型的元素如何管理。

扁平风格

这样风格下,所有的元素都塞到一个proto文件中,在文件名上加入版本号,用作区分不同版本,便于依赖不同版本的proto文件。

|--repo
  |--mainsite
     |--srv-a.v1.proto
     |--srv-b.v2.proto

文件内元素可以按照段落一样归类管理,但是疏于管理的话,比较容易乱放。目录内文件太多,版本多的情况下反而显得目录内文件杂乱,服务的归属业务线也会不清晰。

Google风格

google 的目录风格比较细致,层次也较多。服务从上层的业务线,到下层的具体服务目录,然后到版本目录后,在对各个类型的资源单独放一个目录。这样的目录比较适合巨大的服务。

Uber风格

.
└── uber
    ├── finance
    │   ├── ccap
    │   │   └── v1
    │   │       └── ccap.proto // package uber.finance.ccap.v1
    │   └── payment
    │       ├── v1
    │       │   └── payment.proto // package uber.finance.payment.v1
    │       └── v1beta1
    │           └── payment.proto // package uber.finance.payment.v1beta1
    └── trip
        ├── v1
        │   ├── trip_api.proto // package uber.trip.v1
        │   └── trip.proto // package uber.trip.v1
        └── v2
            ├── trip_api.proto // package uber.trip.v2
            └── trip.proto // package uber.trip.v2

proto按照不同的业务线进行了划分,到具体具体服务目录之后再按照版本划分,版本目录是下再按照资源的不同写入不同的文件。

0x04 如何选择

个人的建议是将协议原始文件proto统一放到一个仓库中,依赖方根据自己需要要生成对应的桩代码,目录风格可以选择Uber的风格。不管哪种风格,只要脚手架工具支持到位都可以用着很爽。

0x05 参考

googleapis

uber