找回密码
 立即注册
查看: 345|回复: 0

grpc服务如何组织protobuf文件的目录结构

[复制链接]
发表于 2022-4-19 10:48 | 显示全部楼层 |阅读模式
先看google cloud的api设计指南

https://cloud.google.com/apis/design/directory_structure
API 服务通常使用 .proto 文件来定义 API 接口,并使用 .yaml 文件来配置 API 服务。每个 API 服务必须在 API 代码库中有一个 API 目录。API 目录应该包含所有 API 定义文件和构建脚本。

每个 API 目录应该具有以下标准布局:

    代码库必要条件
      BUILD:构建文件。METADATA:构建元数据文件。OWNERS:API 目录所有者。README.md:有关 API 服务的常规信息。

    配置文件
      {service}.yaml:基准服务配置文件,google.api.Service proto 消息的 YAML 表示法。prod.yaml:生产环境增量服务配置文件。staging.yaml:模拟环境增量服务配置文件。test.yaml:测试环境增量服务配置文件。local.yaml:本地环境增量服务配置文件。

    文档文件
      doc/*:技术文档文件。它们应采用 Markdown 格式。

    接口定义

    • v[0-9]/:每个这样的目录都包含 API 的主要版本,主要是 proto 文件和构建脚本。
      {subapi}/v[0-9]/:每个 {subapi} 目录都包含子 API 的接口定义。每个子 API 可以有自己的独立主要版本。type/:proto 文件,包含在不同 API 之间、同一 API 的不同版本之间或 API 与服务实现之间共享的类型。type/ 下的类型定义一旦发布就不应该有破坏性更改。


看一个例子:
# 留意这里的v1目录中为proto文件googleapis/google/example/endpointsapis$ tree -A.├── BUILD.bazel├── endpointsapis.yaml├── goapp│   ├── app.yaml│   └── main.go├── prod.yaml├── README.md├── staging.yaml└── v1    └── workspace.proto再看 Uber Protobuf Style Guide V2

https://github.com/uber/prototool/blob/dev/style/README.md

Files should be stored in a directory structure that matches their package sub-names. All files in a given directory should be in the same package.

The following is an example of this in practice.
.└── 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初步结论:按照package + version的层次组织目录

综合上面google和uber的规范,可以得出结论,应该按照package + version的层次来组织protobuf文件的目录结构
举例我们新建一个项目:styx
那么protobuf应该是如下的结构
$ tree -A.└── styx    └── v1        └── service.protopython项目目录

一个python项目的目录结构,按照标准,以styx为例,应该如下所示:
styx$ tree  -A$ tree  -A styxstyx├── README.md├── setup.py└── styx    ├── front    │   ├── index.py    │   └── __init__.py    ├── __int__.py    └── logic        └── user.py
项目目录为styx, 内层再有一个styx作为python的package, package作为整理部署,可以pip install,或放到指定的目录运行

那么styx项目中加入grpc服务,我们上面定义的protobuf文件的目录树应该放到哪里呢,生成的stub文件又该如何引用呢?
参考下envoy

envoy/api$ tree  -A -L 2....├── CONTRIBUTING.md├── envoy│   ├── admin│   ├── annotations│   ├── api│   ├── config│   ├── data│   ├── extensions│   ├── service│   ├── type│   └── watchdog├── examples│   └── service_envoy├── README.md...
如上所示, envoy在项目目录下有个子目录api,  下面放所有的proto文件,也是按照package + version的结构,如:envoy/service/health/v3/hds.proto
api目录还会mirror到另外一个独立的仓库: https://github.com/envoyproxy/data-plane-api.git

参考这个方式,styx的完整结构应该是
$ tree styx -Astyx├── api│   └── styx│       └── v1│           └── service.proto├── README.md├── setup.py└── styx    ├── front    │   ├── index.py    │   └── __init__.py    ├── __int__.py    └── logic        └── user.py确定proto文件目录结构

不同于envoy,业务项目可能会对外提供不同的接口,可能有针对前端的restful接口,有针对内部的grpc接口,api这个目录名意义不明
可以改为protos,目录结构如下:
$ tree styx -Astyx├── protos│   └── styx│       └── v1│           └── service.proto├── README.md├── setup.py└── styx    ├── front    │   ├── index.py    │   └── __init__.py    ├── __int__.py    └── logic        └── user.pyprotobuf生成的stub文件

接下里的问题是,protobuf生成的stub文件应该如何引用? 如果作为一个独立pip包发布,那么引用的方式应该是什么?
styx本身部署的package是styx,  那stub代码应该要区分下才比较合适

envoy的api在pypi上有2个package,如下:

pip install python-envoy-protobuf-installer
from envoy_proto.envoy.config.filter.accesslog.v2.accesslog_pb2 import AccessLogfrom envoy_proto.envoy.config.accesslog.v2.file_pb2 import FileAccessLogfrom envoy_proto.envoy.api.v2.core.config_source_pb2 import ConfigSourcefrom envoy_proto.envoy.api.v2.listener.listener_pb2 import (  Filter,  FilterChain,)
pip install envoy-data-plane
import envoy_data_plane.envoy.api.v2 as envoy
可以看到都额外包了个不同的package name
参考这个内容, 我们可以部门内部定个标准,所有stub代码放在统一的namespace下, 比如baorpc, 以styx为例:
import baorpc.styx.v1.service_pb2_grpc
假如内部的grpc接口使用package: internal, 那么styx的的结构如下:
$ tree styx -Astyx├── protos│   └── styx│       └── v1│           └── service.proto├── README.md├── setup.py└── styx    ├── front    │   ├── index.py    │   └── __init__.py    ├── internal    │   └── server.py    ├── __int__.py    └── logic        └── user.py
style/internal/server.py的内容示例:
#-*- coding: utf8 -*-from baogrpc.styx.v1.service_pb2_gprc import StyxServicerdef serve():    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))    # add servicer to server    server.add_insecure_port('[::]:50051')    server.start()    server.wait_for_termination()if __name__ == '__main__':    serve()ci自动push protos目录的变动到中央仓库

ci的实现参考envoy的做法 https://github.com/envoyproxy/envoy/blob/main/ci/api_mirror.sh:
#!/bin/bashset -eCHECKOUT_DIR=../data-plane-apiMAIN_BRANCH="refs/heads/main"API_MAIN_BRANCH="main"if [[ "${AZP_BRANCH}" == "${MAIN_BRANCH}" ]]; then  echo "Cloning..."  git clone git@github.com:envoyproxy/data-plane-api "$CHECKOUT_DIR" -b "${API_MAIN_BRANCH}"  git -C "$CHECKOUT_DIR" config user.name "data-plane-api(Azure Pipelines)"  git -C "$CHECKOUT_DIR" config user.email data-plane-api@users.noreply.github.com  # Determine last envoyproxy/envoy SHA in envoyproxy/data-plane-api  MIRROR_MSG="Mirrored from https://github.com/envoyproxy/envoy"  LAST_ENVOY_SHA=$(git -C "$CHECKOUT_DIR" log --grep="$MIRROR_MSG" -n 1 | grep "$MIRROR_MSG" | \    tail -n 1 | sed -e "s#.*$MIRROR_MSG @ ##")  echo "Last mirrored envoyproxy/envoy SHA is $LAST_ENVOY_SHA"  # Compute SHA sequence to replay in envoyproxy/data-plane-api  SHAS=$(git rev-list --reverse "$LAST_ENVOY_SHA"..HEAD api/)  # For each SHA, hard reset, rsync api/ and generate commit in  # envoyproxy/data-plane-api  API_WORKING_DIR="../envoy-api-mirror"  git worktree add "$API_WORKING_DIR"  for sha in $SHAS  do    git -C "$API_WORKING_DIR" reset --hard "$sha"    COMMIT_MSG=$(git -C "$API_WORKING_DIR" log --format=%B -n 1)    QUALIFIED_COMMIT_MSG=$(echo -e "$COMMIT_MSG\n\n$MIRROR_MSG @ $sha")    rsync -acv --delete --exclude "ci/" --exclude ".*" --exclude LICENSE \      "$API_WORKING_DIR"/api/ "$CHECKOUT_DIR"/    git -C "$CHECKOUT_DIR" add .    git -C "$CHECKOUT_DIR" commit -m "$QUALIFIED_COMMIT_MSG"  done  echo "Pushing..."  git -C "$CHECKOUT_DIR" push origin "${API_MAIN_BRANCH}"  echo "Done"fi
不同项目在中央仓库的结构,可以使用项目的仓库名+branch组合作为中央仓库的branch,可以再讨论
ci也可以考虑自动生成pip包
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-6 12:55 , Processed in 0.133517 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表