LoginSignup
1

More than 1 year has passed since last update.

gRPCの各言語向けパッケージ生成を一括管理する

Posted at

この記事は DeNA Advent Calendar 2021 の10日目です

11/30にリリースした PLAYBACK 9 では、クライアントとサーバーとのやり取りに gRPC を利用しています。
gRPCではIDLを利用してAPIを表現でき、クライアント及びサーバーのスタブコードを自動生成できるため非常に簡単に利用することができます。

しかしながら、生成したコードをどのように管理するかどうかは各自に委ねられており、ベストプラクティスが定まっているわけではありません。
インターネットでも様々な方法が公開されており、一番よく見かけるのは Git の Submodules を使い、共通の proto ファイルから各自コード生成を行うという方法です。

この方法は簡単ではありますが、

  • コード生成の責務が各利用者に発生する
  • 各言語で、どの時点のコードを利用しているか分かりづらい

といった問題があります。

この問題に対して、GitHub Actions と各言語で提供されているパッケージシステムを利用して解決します。
この解決方法により、クライアントおよびサーバーは通常のパッケージを利用するのと同じように生成されたコードを利用できるようになるため、コード生成の知識が必要ではなくなります。
また、セマンティックバージョニングにてバージョン指定するため、複数の環境があったとしてもバージョン番号によってどの時点で生成されたコードを利用しているかの確認が容易になります。

今回の例では、クライアントに TypeScript、サーバーに Go を利用して紹介していきます。
TypeScriptからサーバーへのアクセスにはgRPC Webを利用しています。

本記事で紹介するコードは以下のリポジトリにて公開しています。

生成されたコードを一括管理するリポジトリを作成する

最初に proto ファイルおよび各言語向けに生成されたコードを保存するリポジトリを作成します。
このリポジトリで、コード生成の責務を全て担うことになります。

リポジトリのディレクトリの構成は以下のようになります。
各言語向けに生成されたコードは各言語の名前の付いたディレクトリに置かれる形になります。
Go 向けに生成されたコードは、パッケージパスをgoではなく、pbにするため、さらに一階層ディレクトリを掘っています。

.
├── Makefile
├── README.md
├── doc
├── go
│   └── pb
├── proto
│   └── service.proto
└── ts

コードの自動生成を行う

GitHub Actions を利用してコードが main ブランチに push された (PRがマージされた) タイミングでコード生成を常に行いコミットするように設定します。
これにより main ブランチでは、常に最新の生成されたコードが反映されていることを保証します。
docker run で自前の docker image を利用していますが、protoc が実行できるのであれば、どのイメージでも問題ないと思います。

github/workflows/build.yml
name: Generate gRPC code
on:
  push:
    branches: main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Regenerate gRPC code
        run: |
          docker run --rm \
          -v `pwd`:/workdir -w /workdir \
          ghcr.io/amothic/protoc --proto_path=proto \
          --go_out=go/pb --go_opt=paths=source_relative \
          --go-grpc_out=go/pb --go-grpc_opt=paths=source_relative \
          --js_out=import_style=commonjs:ts --ts_out=service=grpc-web:ts \
          service.proto
      - name: Commit
        run: |
          [[ ! $(git diff --exit-code go ts) ]] && echo "Nothing to commit." && exit 0
          git config user.name "gRPC Bot"
          git pull
          git add go ts
          git commit -m "chore: regenerate grpc code"
          git push

パッケージとしてリリースするための準備を行う

go ディレクトリと ts ディレクトリでパッケージとしてリリースするための準備を行います。

Go

goディレクトリ以下に新たにgo.modファイルおよびgo.sumファイルを作成します。

$ go mod init github.com/OWNER/your-service-proto/go
$ go mod tidy

作成が終わったら、commitしてpushしておきます。

TypeScript

tsディレクトリ以下に新たにpackage.jsonファイルを作成します。

ts/package.json
{
  "name": "@OWNER/your-service-proto",
  "version": "0.0.0",
  "repository": "git@github.com:OWNER/your-service-proto.git"
}

必要なパッケージを追加し、package-lock.jsonを作成します

$ npm install --package-lock-only @improbable-eng/grpc-web google-protobuf @types/google-protobuf

また、github packageにアップロードするため以下の.npmrcファイルも作成します

ts/.npmrc
@OWNER:registry=https://npm.pkg.github.com

セマンティックバージョンの更新を行う

更新するための最初のバージョンタグを作成します

$ git tag v0.0.1
$ git push origin v0.0.1

その後、バージョン番号を更新する GitHub Actions を追加します

こちらの GitHub Actions は、major, minor, patch のいずれかを渡すことで既存の Git Tag のバージョンをインクリメントして新たな Git Tag を打ってくれます。
major, minor, patch の文字列は、GitHub Actions の workflow_dispatch を利用して開発者が任意のタイミングで渡すこととします。

image.png

name: release
on:
  workflow_dispatch:
    inputs:
      method:
        description: |
          Which number to increment in the semantic versioning.
          Set 'major', 'minor' or 'patch'.
        required: true
jobs:
  tag:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: kyoh86/git-vertag-action@v1.1
        with:
          method: ${{ github.event.inputs.method }}
          push: true

バージョン番号を参照して、パッケージのリリース作業を行う

更新されたバージョンタグを利用して、各言語のリリース作業を行なっていきます。

Go

Go のリリースは、通常Git Tag を打つだけなのですが、サブディレクトリに生成されたコードが置かれているので、サブディレクトリのパスを Tag に追加する必要があります

jobs:
  golang:
    needs: tag
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      # サブディレクトリにGoのモジュールがあるため、サブディレクトリのパスを先頭に付けたTagを追加で発行する
      - name: go module version tag
        run: |
          TAG_VERSION=$(git describe --tags --abbrev=0 --match='v*.*.*')
          MODULE_VERSION=go/$TAG_VERSION
          git tag $MODULE_VERSION
          git push origin $MODULE_VERSION

TypeScript

Git Tag を参照して、package.json を更新し、リリース作業を実施します。
npm パッケージは、アクセスコントロールが容易な GitHub Packages に公開しています。

jobs:
  typescript:
    needs: tag
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: ts package version tag
        id: version
        run: |
          PACKAGE_VERSION=$(git describe --tags --abbrev=0 --match='v*.*.*' | sed -e 's/v//')
          echo "::set-output name=package_version::${PACKAGE_VERSION}"
      - name: bump version
        run: |
          jq '.version|="${{ steps.version.outputs.package_version }}"' ts/package.json > tmp
          mv tmp ts/package.json
      - name: commit package.json
        run: |
          [[ ! $(git diff --exit-code ts/package.json) ]] && echo "Nothing to commit." && exit 0
          git config user.name "gRPC Bot"
          git pull
          git add ts/package.json
          git commit -m "chore: bump version to ${{ steps.version.outputs.package_version }}"
          git push
      - uses: actions/setup-node@v2
        with:
          node-version: '14'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@OWNER'
      - run: npm publish
        working-directory: ./ts
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

生成したパッケージの利用

生成したパッケージを各言語で利用する方法について紹介します。

Go

protoのディレクトリをcloneできる状態なら、GitHubから直接取得することが可能です

// private repoの場合
$ export GOPRIVATE=github.com/OWNER

$ go get -u github.com/OWNER/your-service-proto/go

TypeScript

  1. GitHub にて、read:packages の権限を与えた Personal Access Token を発行します (private repoの場合)
  2. npm login (private repoの場合)

    $ npm login --scope=@OWNER --registry=https://npm.pkg.github.com
    
    > Username: USERNAME
    > Password: TOKEN
    > Email: PUBLIC-EMAIL-ADDRESS
    
  3. package.jsonと同じディレクトリに.npmrcを作成します

    @OWNER:registry=https://npm.pkg.github.com
    
  4. npm install

    $ npm install @OWNER/your-service-proto
    

npmレジストリの利用 - GitHub Docs

終わりに

GitHub Actions と各言語で提供されているパッケージシステムを利用して、生成されたコードをシンプルに利用できるようになりました。
他にも良い方法などあればコメントなどで指摘していただければ幸いです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1