API 工程化分享( 二 )


怎么迁入呢?我们的想法是在某一个微服务的 Proto 目录下 , 把自己的 Proto 文件管理起来 , 然后自动同步进去 , 就相当于要写一个插件 , 可以自动复制到 API 仓库里面去 。做完这件事情之后 , 我们又分了 api.go , api.JAVA , git submodule , 就是把这些代码使用 Google protobuf , protoc 这个编译工具生成客户端的调用代码 , 然后推到另一个仓库 , 也就是把所有客户端调用代码推到一个源码仓库里面去
Proto 独立同步方式

API 工程化分享

文章插图
 
移动端采用自定义工具方式 , 在同步代码阶段 , 自动更新最新的 proto 仓库到 worksapce 中 , 之后依赖 bazel 进行构建整个仓库
  • 业务代码中不依赖 target 产物 , 比如 objective-c 的 .h/.a 文件 , 或者 Go 的 .go 文件(钻石依赖、proto 未更新问题)
源码依赖会引入很多问题
  • 依赖信息丢失
  • proto 未更新
  • 钻石依赖
依赖信息丢失在你的工程里面依赖了其他服务 , 依赖信息变成了源码依赖 , 你根本不知道依赖了哪个服务 , 以前是 protobuf 的依赖关系 , 现在变成了源码依赖 , 服务依赖信息丢失了 。未来我要去做一些全局层面的代码盘点 , 比方说我要看这个服务被谁依赖了 , 你已经搞不清楚了 , 因为它变成了源码依赖
proto 未更新如果我的 proto 文件更新了 , 你如何保证这个人重新生成了 .h/.a 文件 , 因为对它来说这个依赖信息已经丢失 , 为什么每次都要去做这个动作呢?它不会去生成 .h/.a 文件
钻石依赖当我的 A 服务依赖 B 服务的时候 , 通过源码依赖 , 但是我的 A 服务还依赖 C 服务 , C 服务是通过集中仓库 bapis 去依赖的 , 同时 B 和 C 之间又有一个依赖关系 , 那么这个时候就可能出现对于 C 代码来说可能会注册两次 , protobuf 有一个约束就是说重名文件加上包名是不允许重复的 , 否则启动的时候就会 panic , 有可能会出现钻石依赖
  • A 依赖 B
  • A 依赖 C
  • A 和 B 是源码依赖
  • A 和 C 是 proto 依赖
  • B 和 C 之间又有依赖
那么它的版本有可能是对不齐的 , 就是有风险的 , 这就是为什么 google basic 构建工具把 proto 依赖的名字管理起来 , 它并没有生成 .go 文件再 checkin 到仓库里面 , 它不是源码依赖 , 它每一次都要编译 , 每次都要生成 .go 文件的原因 , 就是为了版本对齐
Proto git submodules 方式经过多次讨论 , 有几个核心认知:
  • proto one source of truth , 不使用镜像方式同步 , 使用 git submodules 方式以仓库中目录形式来承载;
  • 本地构建工具 protoc 依赖 go module 下的相对路径即可;
  • 基于分支创建新的 proto , submodules 切换分支生成 stub 代码 , 同理 client 使用联调切换同一个分支;
  • 维护 Makefile , 使用 protoc + go build 统一处理;
  • 声明式依赖方式 , 指定 protoc 版本和 proto 文件依赖(基于 BAZEL.BUILD 或者 Yaml 文件)
proto one source of truth如果只在一个仓库里面 , 如果只有一个副本 , 那么这个副本就是唯一的真相并且是高度可信任的 , 那如果你是把这个 proto 文件拷来拷去 , 最终就会变得源头更新 , 拷贝的文件没办法保证一定会更新
镜像方式同步实际上维护了本地微服务的目录里面有一个 protobuf 的定义 , 镜像同步到集中的仓库里面 , 实际上是有两个副本的
使用 git submodules 方式以仓库中目录形式来承载git submodules 介绍
子模块允许您将 Git 存储库保留为另一个 Git 存储库的子目录 。这使您可以将另一个存储库克隆到您的项目中并保持您的提交分开 。
API 工程化分享

文章插图
 
图中 gateway 这个目录就是以本地目录的形式 , 但是它是通过 git submodules 方式给承载进来的


推荐阅读