増えすぎた GitHub Actions を「3 層アーキテクチャ」で整理した話
はじめに
GitHub Actions (GHA)、便利ですよね。
便利なんですが、じゃんじゃん作るとワークフローが増えてエラいことになりませんか?私はなりました。
- 環境ごとに似たようなワークフローが乱立し、保守が大変
- 変更を加える際、複数のファイルを修正する必要がある
- ワークフローの構造がアプリケーションごとに異なり、認知負荷が高い
- そもそも数が多すぎて何が何やら...
GHA は特にこう構成すれば良い、みたいなプラクティスやフレームワークが無いので、リポジトリの規模が大きく複雑いなってくるにしたがい、GHA も複雑になってしまいがちだと思います。
本記事では、そのような状況から脱却するべく、 3 層アーキテクチャ を採用してリファクタリングした事例を紹介します。
対象読者
- GitHub Actions を使い倒している方
- 割と大きめな規模の GitHub Actions を構成している方
- 大量のワークフローにお悩みの方
課題: 大量のワークフロー乱立
リファクタリング前は、以下のように環境ごと、アプリごとにワークフローが分かれていました。
❌ 変更前
deploy-app1-prod.yml
deploy-app1-stg.yml
deploy-app1-dev.yml
deploy-app2-prod.yml
deploy-app2-stg.yml
deploy-app2-dev.yml
...
じゃんじゃん増えたワークフローの様子はこちらです。ひぃ... ↓

この構成には以下のような問題がありました:
- 重複コードが多い: 各環境のワークフローでほぼ同じロジックを記述
- 保守コストが高い: 修正時に複数ファイルを更新する必要
- ロジックが統一されていない: app ごとに同じことをしているはずなのに微妙にロジックが違う
解決策: 3 層アーキテクチャの導入
これらの課題に対し、 責務を明確に分離した 3 層アーキテクチャ を採用しました。
✅ 変更後
_deploy-app1.yml (Reusable Workflow)
↑
├─ on_release_deploy-app1-prod.yml (env: prod)
├─ on_push_deploy-app1-stg.yml (env: stg)
└─ on_dispatch_deploy-app1-dev.yml (env: dev)
_deploy-app2.yml (Reusable Workflow)
↑
├─ on_release_deploy-app2-prod.yml (env: prod)
├─ on_push_deploy-app2-stg.yml (env: stg)
└─ on_dispatch_deploy-app2-dev.yml (env: dev)
...
3 層アーキテクチャの全体像
名称が安直なのはご容赦ください 🙏 変にもじるよりかはストレートで分かりやすいだろうと思ってます。
各層の役割と実装
1. Trigger 層 (トリガー層)
責務: 外部イベントを受け取り、環境固有のパラメータを設定する
Trigger 層は、GitHub のイベント (release, push, workflow_dispatch) を受け取るエントリーポイントです。デプロイロジックは一切含まず、パラメータの定義のみ を行います。
環境別トリガーの固定化
各環境で使用するトリガーを固定することで、一貫性を確保します。環境とトリガーとの対応は GitHub Flow をベースにしています。
| 環境 | トリガー | タイミング | ファイル名 |
|---|---|---|---|
| prod | release |
GitHub リリース作成時 | on_release_deploy-xxx-prod.yml |
| stg | push |
main ブランチへのマージ時 | on_push_deploy-xxx-stg.yml |
| dev | workflow_dispatch |
手動実行 | on_dispatch_deploy-xxx-dev.yml |
実装例: stg 環境の Trigger 層
name: "[app1/stg] Build & Deploy"
on:
push:
branches:
- main
paths:
- "services/app1/**"
env:
SERVICE: app1
ENVIRONMENT: stg
jobs:
vars:
runs-on: ubuntu-latest
outputs:
env: ${{ env.ENVIRONMENT }}
account_id: ${{ steps.get_account_config.outputs.account_id }}
deploy_role: ${{ steps.get_account_config.outputs.deploy_role }}
steps:
- uses: actions/checkout@v5
- id: get_account_config
uses: ./.github/actions/get_account_config
with:
service: ${{ env.SERVICE }}
env: ${{ env.ENVIRONMENT }}
deploy_app:
needs: vars
uses: ./.github/workflows/_deploy-app1.yml
with:
env: ${{ needs.vars.outputs.env }} # Reusable Workflow では inputs に環境変数を受け取れないため、前ジョブの出力にして渡している
account_id: ${{ needs.vars.outputs.account_id }}
deploy_role: ${{ needs.vars.outputs.deploy_role }}
ポイント:
- 環境固有の情報 (SERVICE, ENVIRONMENT) のみを定義
- Reusable Workflow への呼び出しに徹する
- デプロイロジックは一切含まない
2. Reusable Workflow 層 (再利用ワークフロー層)
責務: ビジネスロジックの実装と、差分チェックやビルド・デプロイのオーケストレーション
この層では、workflow_call で呼び出される再利用可能なワークフローを定義します。複数の環境から共通して利用されるため、環境に依存しない汎用的な実装 が重要です。
実装例: Reusable Workflow
name: "[app1] Build & Deploy (Reusable Workflow)"
on:
workflow_call:
inputs:
env:
required: true
type: string
description: "Environment name"
account_id:
required: true
type: string
description: "Account ID for deployment"
deploy_role:
required: true
type: string
description: "IAM role ARN for deploy app1"
deploy_all:
required: false
type: boolean
default: false
description: "Deploy all components regardless of diff"
jobs:
check_diff:
runs-on: ubuntu-latest
outputs:
diff_backend: ${{ steps.diff_backend.outputs.changed_files }}
diff_frontend: ${{ steps.diff_frontend.outputs.changed_files }}
steps:
- uses: actions/checkout@v5
# バックエンドの差分チェック
- id: diff_backend
if: ${{ !inputs.deploy_all }}
uses: ./.github/actions/get_diff
with:
file_patterns: |
services/backend/**
ignore_file_patterns: |
services/backend/**/*.md
# フロントエンドの差分チェック
- id: diff_frontend
if: ${{ !inputs.deploy_all }}
uses: ./.github/actions/get_diff
with:
file_patterns: |
services/frontend/**
ignore_file_patterns: |
services/frontend/**/*.md
# 差分がある場合のみバックエンドをデプロイ
deploy_backend:
needs: check_diff
# 前ジョブの一部がキャンセルされても実行できるように !(failure() || cancelled()) を指定
if: ${{ ! (failure() || cancelled()) && (needs.check_diff.outputs.diff_backend != '' || inputs.deploy_all) }}
uses: ./.github/workflows/_deploy_backend.yml
with:
env: ${{ inputs.env }}
account_id: ${{ inputs.account_id }}
# 差分がある場合のみフロントエンドをデプロイ
deploy_frontend:
needs: check_diff
if: ${{ ! (failure() || cancelled()) && (needs.check_diff.outputs.diff_frontend != '' || inputs.deploy_all) }}
uses: ./.github/workflows/_deploy_frontend.yml
with:
env: ${{ inputs.env }}
account_id: ${{ inputs.account_id }}
ポイント:
- 標準的な入力パラメータ (
env,account_id,deploy_allなど) を受け取る - 差分チェックの結果に基づいてデプロイを制御
- 環境に依存しない汎用的な実装
3. Composite Action 層 (コンポジットアクション層)
責務: 基本的な操作を組み合わせた再利用可能なアクションの提供
この層では、.github/actions/ 配下に配置される共通処理を Composite action として定義します。実装例については OSS として様々な action が提供されているため本記事では割愛します。
実装のポイント
1. 命名規則の統一
ワークフローファイルは、トリガー種別に応じたプレフィックスを付与します。
| トリガー種別 | プレフィックス | 例 |
|---|---|---|
| release | on_release_ |
on_release_deploy-xxx-prod.yml |
| push | on_push_ |
on_push_deploy-xxx-stg.yml |
| workflow_dispatch | on_dispatch_ |
on_dispatch_deploy-xxx-dev.yml |
| workflow_call | _ |
_deploy-xxx.yml |
この命名規則により、ファイル名から 役割とトリガーが一目で分かる ようになります。
2. 標準インターフェースの定義
Reusable Workflow は、以下の標準的な入力パラメータを受け取ります。
on:
workflow_call:
inputs:
env:
required: true
type: string
description: "Environment name"
account_id:
required: true
type: string
description: "Account ID for deployment"
deploy_role:
required: true
type: string
description: "IAM role ARN for deploy app1"
deploy_all:
required: false
type: boolean
default: false
description: "Deploy all components regardless of diff"
この標準化により、新規サービス追加時のパターンが明確 になります。
3. ドキュメントの充実
ここまでの内容を開発ガイドラインとしてまとめ、チームメンバーに共有します。
- 3 層アーキテクチャの概要と各層の責務
- 環境別トリガー管理の方針
- 命名規則
- 差分ベースデプロイの仕組み
- 標準インターフェース
- 新規サービス追加方法
- 検証方法
これにより、チーム全体で 一貫した実装方針を共有 できます。
導入による効果
まだ導入したてではありますが、以下の効果を期待しています。
- 責務の明確な分離: 各層が単一の責務を持つ
- 再利用性の向上: Reusable Workflow と Composite Action による共通化
- 保守性の向上: 変更影響範囲が明確で、修正が容易
- 拡張性: 新規サービス追加時のパターンが明確
さいごに
GHA の 整理の仕方の一例として、3 層アーキテクチャについて紹介しました。GHA のワークフロー管理でお困りの方の参考になれば幸いです。
もし、他にこんなプラクティス導入してるよ!こんな方法知ってるよ!という方がいましたら、是非コメントいただけますと嬉しいです。
謝辞
これらのアイディアは Claude Code に壁打ちしてもらいながら構築し、本記事も一部 Claude Code に手伝ってもらっています。
いつも大変お世話になっております 🙇
リアルタイム法人調査システム「SimpleCheck」を開発・運営するシンプルフォーム株式会社の開発チームのメンバーが、日々の開発で得た知見や試してみた技術などについて発信していきます。 Publication 運用への移行前の記事は zenn.dev/simpleform からご覧ください。
Discussion