EMoshU Blog
2025/09/09
2025-09-09
Ritsuki

業務のためにgRPCを学んで理解した事と実務におけるさらなる効率化の話【応用編】

gRPCから少し離れ、GitHub Actionsを利用した「コード生成コマンドの実行の自動化」を行います。

実際の運用でgRPCを利用し始める場合に覚えておくと役に立つかも知れません。

こんにちは。サーバーエンジニアのりつきです。

 

当記事は【基礎編】および【実装編】と題した記事の続きですのでまだ読んでいない方はそちらの記事を読んでからご覧ください。

 

 

 

今回はgRPCを実際にプロジェクトに利用するにあたりより開発効率を上げることができる手段として、

GitHub Actionsを利用しprotocコマンドを自動実行させる方法について紹介・解説する記事になります。

 

gRPCそのものからは離れた内容になりますが、是非読んでいただけると幸いです。

 

 

注意

 

GitHub Actionsの利用には使用状況に応じて料金が発生します。

 

今回作成するワークフローは一度の実行で1分かからないので基本的には問題ありませんが、GitHub Freeプランだと本記事執筆時で月2,000分までが無料で利用できる範囲なのでご注意ください。

 

料金体系について詳しくはこちら

 

 

 

開発における課題

【実装編】までの内容でProtocol Buffersを用いたgRPCサーバー・クライアントの開発についてはある程度解説しました。

 

しかし、このまま実務で利用しようとするといくつか問題点が発生するかと思います。

 

 

その問題点とは「毎回protocコマンドを実行して生成されたコードをgitに上げるのは結構手間」というものです。

 

より具体的に言うと下記のような問題などが起こり得ます。

 

  1. コードの生成やステージングへの追加(git add)が漏れていた場合、実際のコードが更新されず実装に利用できない
  2. コードの更新が漏れている状態から追加で.protoファイルを修正してコードの生成をした時、その変更とは関係ないコードが生成され差分が検知しづらくなる
  3. 同じ.protoファイルを同時に更新したとき、生成後のコードがコンフリクトする

 

 

そこで、GitHub Actionsを利用して.protoファイルが更新されたときに自動でprotocコマンドの実行および変更のコミットを行うことで上記の問題を以下のように解消できます。

 

  1. 自動実行で漏れをなくす
  2. 漏れがなくなるので作業と関係ない差分が検出されなくなる
  3. 作業ブランチでは生成後のコードに変更を加えないのでコンフリクトが発生しない

 

このように手動で行った時に発生し得る問題を解決し、開発効率の向上を目指します。

 

 

 

protocコマンドの自動実行

早速GitHub Actionsに実行させるワークフローを記述していきます。

まずは examgrpc/以下に下記のファイルを用意してください。

 


- examgrpc/
  - .github/
    - workflows/
      - generate-pb.yaml : このファイルを追加
  - pkg/
    - grpc/
  - proto/
    - message.proto
    - service.proto

 

 

追加したファイルの中身は下記になります。

 

generate-pb.yaml


# grpcのコード生成を行うワークフロー
name: Generate gRPC codes
on: 
  push:
    branches:
      - main

jobs:
  # protoの差分を確認するジョブ
  check-proto-diff:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      changed_files_count: ${{steps.check_diff.outputs.changed_files_count}}
    steps:
      # gitユーザーの設定
      - name: Setup Git User
        shell: bash
        run: |
          git config --global user.email "action@github.com"
          git config --global user.name "GitHub Action"
      
      # コードのチェックアウト
      - name: Checkout codes
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # protoの差分を確認
      - name: check proto diff
        id: check_diff
        run: |
          CHANGED_FILES_COUNT=$(git diff --name-only HEAD^ proto | wc -l)
          echo "$CHANGED_FILES_COUNT file was changed"
          echo "changed_files_count=$CHANGED_FILES_COUNT" >> "$GITHUB_OUTPUT"

  # grpcのコード生成を行うジョブ
  generate-grpc-codes:
    needs: [check-proto-diff]
    if: needs.check-proto-diff.outputs.changed_files_count != '0'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      # gitユーザーの設定
      - name: Setup Git User
        shell: bash
        run: |
          git config --global user.email "action@github.com"
          git config --global user.name "GitHub Action"
      
      # コードのチェックアウト
      - name: Checkout codes
        uses: actions/checkout@v4

      # goのセットアップ
      - name: Setup go
        uses: actions/setup-go@v5
        with:
          go-version-file: 'go.mod'

      # protocコマンドのインストール
      - name: Install protoc
        uses: arduino/setup-protoc@v3
        with:
          version: "29.3"

      # コード生成
      - name: Generate Codes
        run: |
          go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36
          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5
          export PATH="$PATH:$(go env GOPATH)/bin"
          protoc ./proto/*.proto --go_out=. --go-grpc_out=. --proto_path=.

      # コードのコミット
      - name: Commit and push Generated Codes
        id: commit_changes
        run: |
          git add pkg/grpc
          git commit -m "gRPC code generate"
          git push origin HEAD:main

 

 

このワークフローの要点は下記の三箇所です。

 

.protoの差分の確認

 


      # protoの差分を確認
      - name: check proto diff
        id: check_diff
        run: |
          CHANGED_FILES_COUNT=$(git diff --name-only HEAD^ proto | wc -l)
          echo "$CHANGED_FILES_COUNT file was changed"
          echo "changed_files_count=$CHANGED_FILES_COUNT" >> "$GITHUB_OUTPUT"

 

 

このstepは直前のコミットと現在のコミットの、proto/以下のファイルの差分のみを比較して差分のあるファイル数をGITHUB_OUTPUTへと出力しています。

 

 

詳しい使い方は公式のリファレンスに記載されていますが、

GITHUB_OUTPUTにファイル数を出力してワークフロー上の別のjobから読み込むことで.protoファイルに変更がない場合(readmeやこのyaml自体の更新など)にコード生成が動かないように制御しています。

 

 

protocコマンドのインストール

 


      # protocコマンドのインストール
      - name: Install protoc
        uses: arduino/setup-protoc@v3
        with:
          version: "29.3"

 

ローカル環境ではhomebrewを利用してコマンドをインストールしていましたが、

GitHub Actionsでprotocコマンドを利用するためのアクションが存在しており、

それを利用することで簡単にワークフロー内でコマンドを利用できます。

 

 

コマンドの実行


      # コード生成
      - name: Generate Codes
        run: |
          go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36
          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5
          export PATH="$PATH:$(go env GOPATH)/bin"
          protoc ./proto/*.proto --go_out=. --go-grpc_out=. --proto_path=.

 

 

基礎編でも利用したパッケージのインストールとコマンドの実行を行います。

 

 

以上のワークフローをmainブランチにpushすると、

ワークフローは起動しますがコードの生成が行われないことが確認できるかと思います。

 

.protoファイルを更新

 

動作確認のため.protoファイルを更新してみましょう。

更新する内容はなんでも問題ありませんが、ここではHelloRequestにパラメータを一つ追加します。

 



// 型の定義
message HelloRequest {
    string message = 1;
    string name = 2; // このパラメータを追加
}


上記の更新をmainにpushするとワークフローが実行され、pkg/grpc/以下のコードが更新されることを確認できます。

 

また、server/やclient/直下で下記のコマンドを実行すると各プロジェクトでも追加したパラメータが利用できるようになっているはずです。

 


$ go get github.com/{GitHubアカウント名}/examgrpc

 

 

タグバージョンの更新

コードの生成の自動化は完了しましたが、このままでは一つ問題が残っています。

 

現状生成後のコードのバージョンがコミットハッシュでしか管理されていないため、別の機能の開発中など最新ではないgRPCのコードが必要な場合にわざわざコミットハッシュを調べる必要があります。

 

そこで、gRPCのコードが更新された時パッケージのタグバージョンを更新し、古いバージョンの取得を容易にしたいと思います。

 

まずは現在のバージョンを保持しておくファイルをプロジェクトに追加します。

 


- examgrpc/
  - VERSION : このファイルを追加
  - .github/
    - workflows/
      - generate-pb.yaml
  - pkg/
    - grpc/
  - proto/
    - message.proto
    - service.proto

 

 

中身は下記のようにしておきます。patchバージョンは自動で更新されますがmajor,minorのバージョンは開発しながら必要に応じて手動で更新します。

 

VERSION


0.0.0

 

 

続けてgenerate-pb.yamlに以下の設定を追加します。

 


# grpcのコード生成とバージョン更新を行うワークフロー
name: Generate gRPC codes
on: 
  push:
    branches:
      - main

jobs:
  # protoの差分を確認するジョブ
  check-proto-diff:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    outputs:
      changed_files_count: ${{steps.check_diff.outputs.changed_files_count}}
    steps:
      # gitユーザーの設定
      - name: Setup Git User
        shell: bash
        run: |
          git config --global user.email "action@github.com"
          git config --global user.name "GitHub Action"
      
      # コードのチェックアウト
      - name: Checkout codes
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # protoの差分を確認
      - name: check proto diff
        id: check_diff
        run: |
          CHANGED_FILES_COUNT=$(git diff --name-only HEAD^ proto | wc -l)
          echo "$CHANGED_FILES_COUNT file was changed"
          echo "changed_files_count=$CHANGED_FILES_COUNT" >> "$GITHUB_OUTPUT"

  # grpcのコード生成とバージョン更新を行うジョブ
  generate-grpc-codes:
    needs: [check-proto-diff]
    if: needs.check-proto-diff.outputs.changed_files_count != '0'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      # gitユーザーの設定
      - name: Setup Git User
        shell: bash
        run: |
          git config --global user.email "action@github.com"
          git config --global user.name "GitHub Action"
      
      # コードのチェックアウト
      - name: Checkout codes
        uses: actions/checkout@v4

      # goのセットアップ
      - name: Setup go
        uses: actions/setup-go@v5
        with:
          go-version-file: 'go.mod'

      # protocコマンドのインストール
      - name: Install protoc
        uses: arduino/setup-protoc@v3
        with:
          version: "29.3"

      # コード生成
      - name: Generate Codes
        run: |
          go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36
          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5
          export PATH="$PATH:$(go env GOPATH)/bin"
          protoc ./proto/*.proto --go_out=. --go-grpc_out=. --proto_path=.


      # コードのコミット
      - name: Commit and push Generated Codes
        id: commit_changes
        run: |
          git add pkg/grpc
          git commit -m "gRPC code generate"
          git push origin HEAD:main

      # --- ここから下の部分を追加 ---------
      # 現在のバージョンの読み込み
      - name: Read current version
        id: current_version
        run: |
          echo $(cat VERSION)
          echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"

      # バージョンの自動更新
      - name: Increment version
        id: increment_version
        run: |
          current_version=${{steps.current_version.outputs.version}}
          IFS='.' read -r -a version_parts <<< "${current_version}"
          major=${version_parts[0]}
          minor=${version_parts[1]}
          patch=${version_parts[2]}
          new_patch=$((patch + 1))
          new_version="$major.$minor.$new_patch"
          echo "new_version=v${new_version}" >> "$GITHUB_OUTPUT"
          echo $new_version > VERSION

      # バージョンを更新してブランチにコミット
      - name: Commit and push updated VERSION
        run: |
          new_version=${{steps.increment_version.outputs.new_version}}
          git add VERSION
          git commit -m "Increment version to ${new_version}"
          git push origin HEAD:main

      # タグのリリース
      - name: Create and push the tag
        run: |
          new_version=${{steps.increment_version.outputs.new_version}}
          git tag $new_version
          git push origin $new_version

 

この部分はgRPCの話とは別枠の小技のようなものなので簡単に解説すると下記のような動作をしています。

 

  1. "VERSION"ファイルをcatコマンドで読み込み、結果をGITHUB_OUTPUTへ出力
  2. 読み込んだ文字列を[.]で分割しmajor,minor,patchバージョンを取得
  3. patchバージョンを+1し、"VERSION"ファイルを上書き
  4. 上書きした"VERSION"ファイルをコミット
  5. タグバージョンのリリース

 

この変更をmainブランチに反映した後、再度proto/以下のファイルを更新してみます。

 

message.proto



// 型の定義
message HelloRequest {
    string message = 1;
    string name = 2;
}

message HelloResponse {
    string resMessage = 1;
}

// --- 下記の型定義を追加
message GoodByeRequest {
    string message = 1;
    string name = 2;
}

message GoodByeResponse {
    string resMessage = 1;
}

 

service.proto


// サービスの定義
service GreetingService {
    // メソッドの定義
    rpc Hello (HelloRequest) returns (HelloResponse);
    rpc GoodBye (GoodByeRequest) returns (GoodByeResponse); // このメソッドを追加
}

 

proto/以下のファイルを更新しmainに取り込むとワークフローが起動します。

 

ワークフローが正常に実行された状態でmainブランチを確認するとVERSIONファイルの中身が"0.0.1"に更新されv0.0.1のタグが作成されていることが確認できます。

 

バージョンタグを設定することにより、生成したコードを利用するプロジェクトで下記のようにバージョンを指定して、必要な更新のみを利用して開発することが可能になります。

 


$ go get github.com/{GitHubアカウント名}/examgrpc@v0.0.1

 

 

以上のようにgRPCおよびProtocolBuffersを開発に利用する際はコード生成の自動化をできるようにしておくと、開発の効率をぐっと上げることが可能です。

 

終わりに

ここまで全三記事を読んでいただき、ありがとうございました。

 

このブログを制作する中でも新たな発見があったので制作自体にも意義があったように思えます。

例えば最近のgRPCの開発ではprotocコマンドではなくbufというツールのCLIを利用するのがモダンらしく、キャッチアップが足りなかったなと思うこともありました。

 

自身の学習内容のアウトプットとして書き始めた記事ですが読んでいただいた皆様の開発や学習の一助となっていれば幸いです。

 

また、EMoshUでは一緒に開発できる仲間を募集しております。

ご興味を持たれましたら、ぜひ募集要項をご覧ください。カジュアル面談なども実施しております。

 

募集要項

 

 

まずは話を聞いてみたいという方へ