見出し画像

複数のプログラミング言語向けSDKを1つのリポジトリで管理する方法

こんにちは🐔
Showcase Gigの林 (howyi) です。

この記事は、Showcase Gig Advent Calendar 2021 4日目の記事です

私たちShowcase Gigが開発・運用しているプラットフォームの管理において、プラットフォーム利用者向けSDKを1つのリポジトリで管理している手法について紹介します。

なぜ複数の言語向けにSDKを用意したいのか、なぜ1つのリポジトリで管理したいのか

現在私たちが構築しているサーバはgRPCで構成されています。
サーバは主に「注文サービスと店舗をつなぐ」ことを目的とした機能を持っています。
例えば「注文」の場合、「注文を受け付けるサービスからデータを受け取り、伝えるべき端末に送信する」といった形です。

「注文を受け付けるサービス」と「伝えるべき端末」の数が少なかったり、同じチームで管理している場合、わざわざ別途SDKを作り保守するよりも、ただprotoを共有したほうが早い、ということもあります。
今回の場合、両方において今後も増え続ける展望があり、チーム外や社外の開発にも利用されるためSDKを作成しました。

また、gRPCのメリットの1つとしてコード生成が公式で充実している事が挙げられます。
単純なgRPCサーバへ接続するためのSDKであれば、コード生成結果をうまく活用すれば各SDKの中身はほとんどが自動生成のコードで済ませられるはずです。
サーバのproto更新があり、SDKに反映したい時、sdkのリポジトリでタグを切れば各言語向けのSDKもバージョンアップできることを目的として、以下のようにサブディレクトリを切りました。

showcase-gig/hoge-sdk
├ proto
│ ├ hoge.proto
│ └ fuga.proto
├ go
│ ├ client.go
│ └ go.mod
├ dart
│ ├ lib/
│ └ pubspec.yaml
├ js
│ ├ src/
│ ├ package.json
│ └ tsconfig.json
└ php
  ├ src/
  └ composer.json

今回はprotoの各言語向けのコードの自動生成については説明せず、サブディレクトリでプライベートパッケージを作ろうとして困ったポイント、解消した方法などを機作します。
以降の説明の内容はgRPCに限らないため、GraphQLやOpenAPIからの自動生成をベースとしたSDKでも参考にできます。

ホスティングしているのはGitHubを前提としています。

Go /go

Go言語は go/v0.1.0 という形でタグを切っておくと、 go get github.com/{リポジトリ}/go@v0.1.0 といった形で go get できます。
プライベートリポジトリの場合は別途GitHubのパーソナルアクセストークンを設定しておきましょう。

GOPRIVATE=github.com/showcase-gig go get github.com/showcase-gig/hoge-sdk/go@v0.1.0

このように、サブディレクトリであってもあまりサブディレクトリを意識せずにプライベートパッケージとして利用できます。

例えば以下のような GitHub Actionsを作り、 v0.1.0 でリリースが作られたときに自動で go/v0.1.0 といったタグも付与すると、専用作業が発生せず楽になります。

name: "On release"
env:
  CI: true
on:
  release:
    types: [published]

jobs:
  go-release:
    name: go release
    runs-on: ubuntu-latest
    steps:
      - name: Set version variable
        id: version
        run: |
          VERSION=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
          echo ::set-output name=version::$VERSION
      - name: checkout
        uses: actions/checkout@v2
      - name: tag to go sdk
        run: |
          git tag go/${{ steps.version.outputs.version }}
          git push origin go/${{ steps.version.outputs.version }}

Dart /dart

Dartは pubspec.yaml でurlとパスを指定できます。こちらもそこまで悩むことはないですね。
cloneになるので、こちらをインストールする際はsshの鍵を設定しておく必要があります。

hoge_sdk:
  git:
    url: git@github.com:showcase-gig/hoge-sdk.git
    path: dart

TypeScript (JavaScript) /js

package.jsonはGitの特定ディレクトリ以下をpackageとして読み込む、といったことは できません
そのため、 GitHub ActionsGitHub Packages を使用し、タグを切ったタイミングでJSディレクトリのパッケージをビルドしアップロードしてみます。
js/package.jsonpublishConfig repository は以下のように設定し、Publish先をnpmからGitHub Packagesにしましょう。

{
  "name": "@showcase-gig/hoge-sdk",
  ~~~~~~~~~~
  "publishConfig": {
    "access": "restricted",
    "registry": "https://npm.pkg.github.com/"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/showcase-gig/hoge-sdk",
    "directory": "js"
  },
  ~~~~~~~~~~
}

GitHub Actionsのworkflowはこうなります。
今回の場合、GitHubのReleaseのタイミングでタグを切り、そのタグでアップロードしています。
on: の部分を変更すれば特定フォーマットのタグをpushしたタイミングや、特定条件でリリースということも可能です。

name: "On release"
env:
  CI: true
on:
  release:
    types: [published]

jobs:
  js-release:
    name: js release
    runs-on: ubuntu-latest
    steps:
      - name: Set version variable
        id: version
        run: |
          VERSION=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
          echo ::set-output name=version::$VERSION
      - name: checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: setup Node
        uses: actions/setup-node@v1
        with:
          node-version: 14.x
          registry-url: 'https://npm.pkg.github.com'
      - name: install
        run: cd js && npm install
      - name: package.jsonのversionを更新
        run: |
          sed -i -e "s/\"version\":.*\"/\"version\": \"${{ steps.version.outputs.version }}\"/" js/package.json
      - name: build
        run: cd js && npm run build
      - name: publish
        run: cd js && npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

アップロードしたパッケージのインストールは GitHub Packagesの公式 をご覧ください

PHP /php

PHPもJSと同様に、Gitのディレクトリを指定した呼び出しはできません。
また、 composer.json がリポジトリ直下にない限りライブラリとして読み込めません。
そのため、ちょっとルール違反ですが今回は「hoge_sdk_php というコピーリポジトリを作り、タグを切るたびコピー先に同期する」という方向とします。
ちなみにこの方法は grpc/grpc-php でも行っている手法です。
GitHub Actionsを使う場合、リポジトリを跨いで作用するためリポジトリにのアクセストークンを発行し、 REPO_ACCESS_TOKEN としてsecretsに登録しておいてください。

hoge_sdkのworkflow (コピー元)

name: release
on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+"
jobs:
  copy-to-repository:
    strategy:
      matrix:
        repo: ['showcase-gig/hoge_sdk_php']
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1
      - run: ls
      - name: Get the version
        run: echo ${GITHUB_REF#refs/tags/}
      - name: dispatch target-updated
        uses: peter-evans/repository-dispatch@v1
        with:
          token: ${{ secrets.REPO_ACCESS_TOKEN }}
          repository: ${{ matrix.repo }}
          event-type: target-released
          client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'

タグを切った際、コピー先リポジトリに、 target-released のイベントを送信しています。
payloadとして、リリースしたタグを送信しています。

hoge_sdk_phpのworkflow (コピー先)

name: release
on:
  repository_dispatch:
    types: [target-released]
jobs:
  copy-from-repository:
    runs-on: ubuntu-latest
    steps:
      - name: Set version variable
        id: version
        run: |
          VERSION=$(echo ${{ github.event.client_payload.ref }} | sed -e "s#refs/tags/##g")
          echo ::set-output name=version::$VERSION
      - name: checkout
        uses: actions/checkout@v2
      - run: mkdir ../tmp_to
      - name: 更新後も残したいファイルを退避
        run: |
          cp README.md ../tmp_to
          cp -r .github ../tmp_to
      - name: ファイル内を全削除
        run: rm -rf *
      - name: コピー元リポジトリをclone
        uses: actions/checkout@v2
        with:
          repository: showcase-gig/hoge_sdk
          path: tmp_from
          ref: ${{ github.event.client_payload.ref }}
          token: ${{ secrets.REPO_ACCESS_TOKEN }}
      - name: 対象ディレクトリを移動
        run: cp -r tmp_from/copy/* ./
      - run: rm -rf tmp_from/
      - name: 退避したファイルを戻す
        run: cp -r ../tmp_to/* .
      - run: git status
      - name: 差分があればpush
        run: |
          git add .
          if ! $(git status -s)
          then
            git config user.name github-actions
            git config user.email github-actions@github.com
            git add .
            git commit -m "Update to version ${{ steps.version.outputs.version }}"
            git push
          fi
      - name: タグを付ける
        run: |
          git tag ${{ steps.version.outputs.version }}
          git push origin ${{ steps.version.outputs.version }}

タグからリポジトリをcloneし、差分があれば反映してpushした後、コピー元同様にタグを切ります。

アップロードしたパッケージのインストールは以下のようにできます。

composer config repositories.showcase-gig/hoge_sdk_php vcs https://github.com/showcase-gig/hoge_sdk_php
composer require showcase-gig/hoge_sdk_php:v0.1.0

実際に動作するサンプルを個人リポジトリに作成しているため、ぜひ参考にしてみてください。

まとめ

サブディレクトリで複数言語のSDKをプライベートで反映する方法を紹介してみました。
同様に困っている方、悩んでいる方の助けとなれば幸いです。

ということで Showcase Gig Advent Calendar 2021 4日目でした!明日もお楽しみに 🍗

Showcase Gigでは多種多様な技術に挑戦する仲間を募集しています!
ぜひチェックしてみてください ✨

この記事が気に入ったらサポートをしてみませんか?