こんにちは、ライクル事業部 エンジニアの菊池@kichionです
去年(2021年)からフロントエンド環境の立ち上げを行い、現在はバックエンドに戻ってきて技術負債の解消などを中心にシステム改善を行っています
ライクルでは早すぎたマイクロサービス化により、コードベースが30近いGithub repositoryに分散しており重複コードが散乱している状態でした
今回はコードベースの共通化策としてgolangで書かれた社内用のライブラリを配布する方法を紹介します
前提
外部API
ライクルでは事業ドメインとしてGoogle Business Profileと呼ばれるGoogle 検索/マップなどにお店の情報を表示するツールを利用します
そのため、各システムからBusiness Profile APIsを使います
開発言語
ライクルでは今回紹介する社内ライブラリと各システムのバックエンド開発言語としてgolangを採用しています
Github Organization
SOTとしてGithub OrganizationのGitHub Team Planを利用しています
基本的にアプリケーションのコードはprivate repositoryで管理されており、Github Actionsの利用もTeamプランの制限内で行っています
解決したかった問題
ライクルでは分散したコードベースがそれぞれ外部のAPI(以下、GBP API)を利用しており、GBP API実行のコードが分散していました
Token利用やリトライ方法(exponential backoffなど)が部分的に重複していたり古いrepositoryではリトライなどが行えないなどの問題がありました
上記、以外にも様々な問題が浮き彫りになりました
- コードがDRYではない
- Token利用やリトライ方法が均一ではないのでシステムの認知負荷が高い
- GBP APIクライアントライブラリがgolangでは存在しない
- GBP API version upの度にコードのコピペが必要になる
事業要望を叶えるためにシステムの数を増やした結果GBP APIのversion up対応が辛くなり、今回の共通化を行うことになりました
社内ライブラリ配布
実際に取り組んだ社内ライブラリ配布の仕組みを紹介します
配布フロー
各項目
- golang private repositoryにtag pushを行う
- tag pushでGithub Actionsを起動する
- Github Actionsから配布先のrepositoryに対してdispatch eventを通知する
- 配布を受けるrepositoryのGithub Actionsを起動する
- golang packageをversion upしてPull Requestを作成する
配布元の構成
Github上でprivate repositoryを作成し、golangでコードを書きます
実際に配布元として行う設定などはGithub Actionsに集約します
Github Actions workflow
Github Actionsで手動トリガーとして用意されているrepository dispatchを利用したjobを組みます
実際にdispatch eventを臆す際には公開されているActionで利用できるものを見つけたので採用します
github/workflows/dispatch-update.yaml
name: dispatch-update on: push: tags: - 'v*' jobs: release: strategy: fail-fast: false matrix: # 配布先が増えるときに追加する repo: ['repo1', 'repo2','repo3','repo4'] name: dispatch runs-on: ubuntu-latest steps: - name: dispatch update module uses: peter-evans/repository-dispatch@v1 with: repository: ${{ OrganizationName }}/${{ matrix.repo }} token: ${{ secrets.REPO_ACCESS_TOKEN }} event-type: update-gbpapi-client
secrets.REPO_ACCESS_TOKEN
にはPersonal access tokenを利用するかGithub App Tokenを利用するか等様々な方法があります
今回はFull control of private repositories
なPersonal access tokenを採用しています
※ Github App Tokenの利用方法を知ったのは弊社テックブログの岸田くんの記事だったのは内緒です
Github App Tokenでの設定については下記のブログをご参照ください
配布先の構成
こちらもGithub上でprivate repositoryを作成しgolangでコードを書きます
配布eventの受け取りとしては以下の2点が必要です
- dispatchを受け付ける
- private repositoryのgolang packageをgo getできるようにする
Github Actions workflow
repository dispatchを受け取りつつ、package updateしたPRを作成します
その際にprivate repositoryからgo getできるようにするため、OAuthトークンを使ってgit参照を行います
また、private repositoryのgolang packageを展開するためにGOPRIVATE
設定を用います
※ CICDでgo getを行う場合でもGOPRIVATE
設定を利用する必要があります
.github/workflows/update-gmbapi-client.yml
name: update-gbpapi-client on: repository_dispatch: types: - update-gbpapi-client jobs: create-pull-request: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: 1.17.3 - name: Set up github token run: git config --global url."https://${{ secrets.LYCLE_GO_MODULES_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" - name: Set current client version id: current run: | current_version=$(go list -m all | grep -o -E "(github.com/{{ OrganizationName }}/gbpapi-client .*)?" | grep -o -E "([0-9]+\.){1}[0-9]+(\.[0-9]+)?" | head -n1) current_major_version=$(echo "${current_version}" | grep -o -E "([0-9]+\.)" | head -n1) current_minor_version=$(echo "${current_version}" | grep -o -E "([0-9]+\.)" | tail -n1) echo "current_major_version=${current_major_version}" >> $GITHUB_ENV echo "current_minor_version=${current_minor_version}" >> $GITHUB_ENV - name: Update gmbapi client run: GOPRIVATE="github.com/{{ OrganizationName }}/*" go get github.com/{{ OrganizationName }}/gbpapi-client@latest - name: go mod tidy run: go mod tidy - name: Set latest client version id: latest run: | latest_version=$(go list -m all | grep -o -E "(github.com/{{ OrganizationName }}/gbpapi-client .*)?" | grep -o -E "([0-9]+\.){1}[0-9]+(\.[0-9]+)?" | head -n1) latest_major_version=$(echo "${latest_version}" | grep -o -E "([0-9]+\.)" | head -n1) latest_minor_version=$(echo "${latest_version}" | grep -o -E "([0-9]+\.)" | tail -n1) echo "latest_major_version=${latest_major_version}" >> $GITHUB_ENV echo "latest_minor_version=${latest_minor_version}" >> $GITHUB_ENV - name: Create Pull Request id: cpr uses: peter-evans/create-pull-request@v3 with: title: "✨ Update GBP API Client" base: staging branch: update-gbpapi-client - name: pull request auto merge if: env.current_major_version == env.latest_major_version && env.current_minor_version == env.latest_minor_version env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh pr merge ${{ steps.cpr.outputs.pull-request-url }} --merge
ライクルチームではマイナーアップデートの場合はオートマージしたいという要求もあったため、バージョン差異を確認してPRをマージするロジックを組んでいます
GOPRIVATE
golang v1.13で追加された設定です
golang v1.13からはデフォルトでsum.golang.orgにある公開のGo checksum databaseと照合して検証するようになっています
対象のモジュールがprivate repositoryにある場合はchecksum databaseに記録されないため、検証が失敗してダウンロードができません
GOPRIVATE
を設定することで対象のパスを検証外のものとして扱うようにできます
終わりに
上記の配布を行って、問題の解消に向けて大きく前進できました
- Token利用やリトライ処理などを共通化できた
- GBP APIのversion upを行いやすくなった
- 配布先のGBP API version up対応を自動作成のブランチから開始しやすくなった
GOPRIVATE
を利用することでAPIのクライアントライブラリ以外の共通処理もライブラリ化しやすくなりました
しかし、pubsubっぽい仕組みの割には配布元のworkflowにリポジトリ名を追加する必要がありそこには課題が残ります
そもそも「Monorepoで良かったよねー」と感じる部分のマージ作業など泥臭い部分の解消は今後も粛々と続けていきます