💂

GraphQLスキーマ変更を安全に:破壊的変更検知と依存Repoへの自動PR作成

に公開

こんにちは。株式会社EVERSTEELで業務委託のエンジニアをしている白木です。

GraphQLスキーマの変更時に発生しがちな破壊的変更の見落としや、フロントエンド・バックエンド間の型定義同期の課題を、GitHub Actionsを活用した自動化によって解決した事例を紹介します。

直面していた課題

GraphQLを活用したマイクロサービスアーキテクチャやフロントエンド・バックエンド分離型のWeb開発において、GraphQLスキーマの変更管理は重要な課題です。

特に以下のような問題に直面していました。

  1. 破壊的変更の見落とし: GraphQLスキーマの変更時に、既存のクライアントを壊してしまう変更(フィールドの削除、必須フィールドの追加など)を見落としがち
  2. 手動での型定義更新: バックエンドでスキーマを変更した後、フロントエンドの型定義を手動で更新する必要があり、作業漏れや遅延が発生
  3. リポジトリ間の同期: バックエンドとフロントエンドを別リポジトリで管理しているため、変更の同期が煩雑
  4. レビュー時の負担: PRレビュー時に、レビュアーが破壊的変更を手動で確認する必要があり、見落としのリスクが存在

これらの問題により、本番環境での意図せぬエラー発生や開発効率の低下を招いていました。

対応したこと

この課題を解決するため、以下の機能を持ったGitHub Actionsを活用した自動化パイプラインを構築しました。

機能フロー:

  1. バックエンドリポジトリでGraphQLスキーマが変更されたPRを検知
  2. GraphQL Schemaの破壊的変更のチェック (破壊的変更がある場合はここでfailedにする)
  3. ローカルでバックエンドサーバーを起動
  4. フロントエンドリポジトリをクローン
  5. GraphQL Code Generatorで新しいスキーマを参照したコード生成
  6. 自動生成コードに変更があればプルリクエストの作成

1. 破壊的変更の検知 についての解説

GraphQLスキーマの破壊的変更を自動検知し、CIを失敗させる仕組みを追加しました。

使用したツール:

GraphQL Inspectorが検出する破壊的変更の例:

✅ 安全な変更(CIが通る):

# Nullable入力フィールドの追加
input UserInput {
  name: String!
  email: String!
+ age: Int  # Nullableなので既存クライアントに影響なし
}

# 新しいクエリの追加
type Query {
  user(id: ID!): User
+ users(limit: Int): [User!]!  # 新規追加は安全
}

❌ 破壊的変更(CIが失敗):

# 必須入力フィールドの追加
input UserInput {
  name: String!
  email: String!
+ password: String!  # 既存クライアントはこのフィールドを送信していない
}

# 既存フィールドの削除
type User {
  id: ID!
  name: String!
- email: String!  # 既存クライアントがこのフィールドを使用している可能性
}

# 既存クエリの削除
type Query {
- user(id: ID!): User  # 既存クライアントが使用中の可能性
}

2. 自動フロントエンドPR作成

GraphQLスキーマ変更時に自動でフロントエンドリポジトリにPRを作成する仕組みを実装しました。

使用したツール・技術:

  • GitHub App: リポジトリ間の認証とPR作成権限の管理
  • GraphQL Code Generator: スキーマから TypeScript 型定義を自動生成
  • GitHub CLI: PRの操作

具体例

全体 GitHub Actionsワークフローの実装 (抜粋)

破壊的変更検知を含むワークフローの実装例です:
※ 実際の運用ではもう少し複雑なコードになっていますが、記事用に重要度が高い部分だけ残しています

name: Run GQL Schema Integration
description: Check Breaking Schema Changes and Generate GQL codegen PR For Frontend

on:
  pull_request:
    branches: [develop]
    types: [opened, synchronize, reopened, ready_for_review]
    paths:
      - "~/schema.gql" # GraphQL Schemaに変更があるプルリクエストの場合のみ実行

jobs:
  # 破壊的変更の検知ジョブ
  check-breaking-schema-changes:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout backend repository
        uses: actions/checkout@v4
      
      # GraphQL Inspectorによる破壊的変更チェック
      - uses: kamilkisiela/graphql-inspector@master
        with:
          schema: 'develop:~/schema.gql' # developブランチのschemaと比較
          fail-on-breaking: true

  # フロントエンドコード生成ジョブ
  update-frontend-codegen:
    needs: check-breaking-schema-changes  # 破壊的変更チェックの成功後に実行
    runs-on: ubuntu-latest
    steps:
      - name: Checkout backend repository
        uses: actions/checkout@v4

      # GitHub App tokenを生成してリポジトリ間アクセスを可能にする
      - name: Generate GitHub App token
        id: generate-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.FRONTEND_REPO_ACCESS_APP_ID }}
          private-key: ${{ secrets.FRONTEND_REPO_ACCESS_PRIVATE_KEY }}
          owner: your-org
          repositories: your-frontend-repo

      # 既存のフロントエンドPRを検索(重複作成を防ぐため)
      - name: Check for existing frontend PR
        id: check-existing-pr
        run: |
          echo "${{ steps.generate-token.outputs.token }}" | gh auth login --with-token
          
          BACKEND_PR_NUMBER="${{ github.event.pull_request.number }}"
          EXISTING_PRS=$(gh pr list --repo your-org/your-frontend-repo --state open --json number,title,body,headRefName --jq ".[] | select(.title | contains(\"GraphQL Schema Update\")) | select(.body | contains(\"#${BACKEND_PR_NUMBER}\"))")
          
          if [ -n "$EXISTING_PRS" ]; then
            EXISTING_PR_NUMBER=$(echo "$EXISTING_PRS" | jq -r '.number')
            EXISTING_BRANCH=$(echo "$EXISTING_PRS" | jq -r '.headRefName')
            echo "EXISTING_PR_NUMBER=$EXISTING_PR_NUMBER" >> $GITHUB_ENV
            echo "EXISTING_BRANCH=$EXISTING_BRANCH" >> $GITHUB_ENV
            echo "HAS_EXISTING_PR=true" >> $GITHUB_ENV
          else
            echo "HAS_EXISTING_PR=false" >> $GITHUB_ENV
          fi

      # バックエンドサーバーの起動
      - name: Setup Node.js and start backend server
        run: |
          cd app
          (... 依存や環境のsetup)

          # 開発サーバーの起動
          yarn dev:gql &
          
          (...必要に応じてサーバーが起動するまで待機)

      # フロントエンドリポジトリでの新スキーマを参照したコード生成 ※1 
      - name: Clone frontend and generate GraphQL code
        run: |
          # フロントエンドリポジトリをクローン
          git clone https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com/your-org/your-frontend-repo.git frontend-repo
          cd frontend-repo
          
          (... 依存や環境のsetup)
          
          # ブランチ準備
          if [ "${{ env.HAS_EXISTING_PR }}" = "true" ]; then
            git checkout "${{ env.EXISTING_BRANCH }}"
            git pull origin "${{ env.EXISTING_BRANCH }}"
          else
            BRANCH_NAME="update-graphql-schema-$(date +%Y%m%d-%H%M%S)"
            git checkout -b "$BRANCH_NAME"
            echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
          fi
          
          # 型定義コード生成
          yarn codegen
          
          # 変更があるかチェック
          if ! git diff --quiet "./src/generated/graphql.ts"; then
            echo "HAS_CODEGEN_CHANGES=true" >> $GITHUB_ENV
            
            # 変更をコミット・プッシュ
            git add src/generated/graphql.ts
            git commit -m "🤖✨ Update GraphQL schema and regenerate types"
            git push origin ${BRANCH_NAME:-${{ env.EXISTING_BRANCH }}}
            
            # PRを作成または更新
            echo "${{ steps.generate-token.outputs.token }}" | gh auth login --with-token
            if [ "${{ env.HAS_EXISTING_PR }}" != "true" ]; then
              gh pr create \
                --title "🤖 GraphQL Schema Update & Codegen" \
                --body "## 🔄 Automated GraphQL Schema Update
                       このプルリクエストは、バックエンドAPIのGraphQLスキーマ変更に伴って自動的に作成されました。(...その他補足情報)"

終わりに

GraphQLスキーマの破壊的変更検知と依存リポジトリへの自動PR作成により、以下のような効果を得ることができました:

開発効率の向上

  • 手動作業によるミスや作業漏れを防止
  • 開発者がビジネスロジックの実装に集中できる環境を実現

品質の向上

  • 破壊的変更の反映前の事前発見により、本番環境でのエラーを未然に防止
  • PRレビュー時の負担軽減と見落としリスクの削減
  • APIの一貫性を自動的に保証

チーム協業の改善

  • バックエンド・フロントエンド間の変更同期が自動化
  • 変更の影響範囲が明確になり、コミュニケーションコストが削減
  • リポジトリを跨いだ変更追跡が容易に

この仕組みは、GraphQLを採用している多くのプロジェクトで応用可能です。
特に、マイクロサービスアーキテクチャやリポジトリが分離された環境では非常に効果的だと思います。

導入を検討されている方へ

この仕組みの導入により、GraphQLスキーマ変更に伴うリスクを大幅に軽減できました。

特に以下のような環境では非常に効果的だと考えます:

  • 複数の開発チームが並行してGraphQLスキーマを変更する環境
  • 破壊的変更による本番障害を未然に防ぎたいプロジェクト
  • フロントエンド・バックエンドが分離されたアーキテクチャ

導入の際は、まず破壊的変更検知から始めて、段階的に自動PR作成機能を追加することをお勧めします。

ここまでお読みいただき、ありがとうございました!


参考リンク:

EVERSTEEL Tech Blog

Discussion