目前公司采用 protocol buffer 作为 IDL,虽然可以根据 API 定义,轻松生成客户端和服务端的代码。但是对于跨项目的接口,会增加项目之间的耦合性。例如A服务对外提供了一个接口,B服务去调用。那么就需要根据A服务的proto文件,生成客户端代码,并拷贝给B。如果联调期间,A服务改动了该接口,还需重复前面的步骤,非常繁琐。
由此引出两个问题,proto文件放在哪合适?调用方如何获取生成的接口客户端代码?
常见的几种解决方案,煎鱼大佬已经描述得很详细了(真是头疼,Proto 代码到底放哪里?),这里不再赘述。
经过查阅资料,总结出适用于我们项目的几种方案。
Java
不同,go build不会将依赖包全部构建到二进制文件里,只会构建项目里实际用到的文件。权衡了下,最终选择方案四。
stages:
- lint
- generate
variables:
BUF_CACHE_DIR: /cache/${CI_PROJECT_PATH}/buf-cache
before_script:
- mkdir -p $BUF_CACHE_DIR
buf_lint:
stage: lint
image: 172.x.x.x/common/buf:1.6.0
tags:
- 172.x.x.x-runner
interruptible: true
script:
- buf mod update
- buf lint --error-format=json --timeout 5m
generate_go_file:
stage: generate
image: 172.x.x.x/common/buf:1.6.0
tags:
- 172.x.x.x-runner
interruptible: true
variables:
TARGET_REPOSITORY_ADDR: git@xxx.com:xxxapis/xxx-api-go.git
TARGET_REPOSITORY: xxx-api-go
script:
- chmod +x ./script/generate-go-file-to-xxx-api-go.sh
- ./script/generate-go-file-to-xxx-api-go.sh
#!/bin/bash
echo "-------------------- 根据 proto 文件生成go代码 --------------------";
buf mod update;
buf generate --timeout 5m;
echo "-------------------- 同步 proto 文件生成的go代码到 ${TARGET_REPOSITORY} 仓库 --------------------";
echo "-------------------- 配置 git ssh,实现免密提交到 ${TARGET_REPOSITORY} 仓库 --------------------";
eval $(ssh-agent -s)
ssh-add <(echo "$CI_AUTO_SYNC_SSH_PRIVATE_KEY");
mkdir -p ~/.ssh;
touch ~/.ssh/config;
echo "StrictHostKeyChecking no" >> ~/.ssh/config;
echo "UserKnownHostsFile /dev/null" >> ~/.ssh/config;
echo "-------------------- 配置 git 用户信息为当前触发流水线的用户 --------------------";
git config --global user.email "$GITLAB_USER_EMAIL";
git config --global user.name "$GITLAB_USER_LOGIN";
echo "-------------------- git clone ${TARGET_REPOSITORY} 仓库,只拉取指定分支的最后一次 commit --------------------";
cd /tmp;
BRANCH=$CI_COMMIT_BRANCH;
if ! (git clone --depth 1 --branch $BRANCH $TARGET_REPOSITORY_ADDR); then
echo "新建分支:$BRANCH"
git clone --depth 1 --branch main $TARGET_REPOSITORY_ADDR;
cd ${TARGET_REPOSITORY};
git checkout -b $BRANCH;
fi
echo "-------------------- 拷贝go文件到 ${TARGET_REPOSITORY} 仓库 --------------------";
rm -rf /tmp/${TARGET_REPOSITORY}/xxx
mv $CI_PROJECT_DIR/apigen/xxx /tmp/${TARGET_REPOSITORY};
cd /tmp/${TARGET_REPOSITORY}
go mod tidy;
echo "-------------------- 提交到 ${TARGET_REPOSITORY} 仓库 --------------------";
cd /tmp/$TARGET_REPOSITORY;
git add .;
git commit -m "sync: 通过 ${CI_PROJECT_PATH} gitlab ci 同步 proto 文件生成的go代码" || true;
git push --set-upstream origin $BRANCH;
echo "-------------------- 同步成功 --------------------";
CI_AUTO_SYNC_SSH_PRIVATE_KEY
:在gitlab配置的变量,具体谷歌gitlab配置ssh# 配置模块信息,包括依赖项
version: v1
deps:
- buf.build/googleapis/googleapis
lint:
use:
- DEFAULT
except:
- FIELD_LOWER_SNAKE_CASE
- RPC_REQUEST_STANDARD_NAME
- RPC_RESPONSE_STANDARD_NAME
- RPC_REQUEST_RESPONSE_UNIQUE
breaking:
use:
- FILE
# 配置protoc生成规则
version: v1
managed:
enabled: true
go_package_prefix:
default: xxx.com/xxxapis/xxx-api-go
except:
- buf.build/googleapis/googleapis
plugins:
- name: go
out: apigen
opt: paths=source_relative
- name: go-grpc
out: apigen
opt:
- paths=source_relative
- require_unimplemented_servers=false
- name: grpc-gateway
out: apigen
opt: paths=source_relative
- name: openapiv2
out: apigen
opt:
- allow_repeated_fields_in_body=true
只是存放通过xxxapis
仓库ci同步过来的文件。
module xxx.com/xxxapis/xxx-api-go
go 1.17
require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3
google.golang.org/genproto v0.0.0-20220707150051-590a5ac7bee1
google.golang.org/grpc v1.47.0
google.golang.org/protobuf v1.28.0
)
require (
github.com/golang/protobuf v1.5.2 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
)
参照google,这两个仓库放到gitlab的xxxapis组下。
这里就直接贴上xxxapis项目的readme。
公司所有 API 定义文件(protocol buffer)统一存放到此仓库。
git submodule
的方式引入 API 大仓?git submodule add https://xxx.com/xxxapis/xxxapis.git
.gitmodules
文件和xxxapis
文件夹。每个项目都引入 API 大仓,会不会浪费空间?
API大仓体积很小的,一个项目的接口定义就几个文本文件。
git submodule
的代码?git submodule
的代码?进入子仓目录,和正常的仓库一样,运行git pull
,git submit
,切记要检查当前所在分支是不是游离的。
go
提交proto文件后,会通过流水线生成对应的go代码,并上传到xxx-api-go
。时间目前测试为半分钟,流水线跑完会有邮件提醒。
go get xxx.com/xxxapis/xxx-api-go@main
java
可使用maven插件,具体请参考maven + protobuf + gRPC + gitlab CI
其他语言
暂未考虑,需要时再扩展吧。
存放 proto文件的目录:
此项目采用Github Flow,持续发布。只有一个长期分支:main,新功能基于main打feature分支,格式为feature/xxx功能,不用带版本号,因为此项目目前没有使用版本号管理,接口版本通过目录来体现。最后提合并请求到main
分支,成功合并后就代表发布了。