モノレポを分割した話:CI/CDと責務分離の設計記録
はじめに
WordPressで技術ブログを運用しています。記事はMarkdownで書き、インポートスクリプトでWordPressに投稿する仕組みです。
本番環境へのリリースを目前に控え、運用設計で壁にぶつかりました。1つのリポジトリに「記事」「インポートスクリプト」「WordPressテーマ」が同居しており、CI/CDをどう設計すべきか分からなくなったのです。
この記事では、モノレポの課題を整理し、リポジトリを3つに分割するに至った設計判断の過程をご紹介します。
モノレポ運用で直面した課題
同居する3つのレイヤー
プロジェクトの構造は以下のようになっていました。
scriptlab/
├── article/ # 記事コンテンツ(Markdown)
├── scripts/ # インポートスクリプト
├── theme/scriptlab-theme/ # WordPressテーマ
└── docker/ # インフラ設定
一見シンプルに見えますが、この構造には問題がありました。
以下の図は、3つのレイヤーの関係を示しています。
graph TB
subgraph "1つのリポジトリ"
MD["記事コンテンツ
(Markdown)"]
IMPORT["インポートスクリプト
(変換処理)"]
WP["WordPressテーマ
(デザイン)"]
end
MD -->|"変換"| IMPORT
IMPORT -->|"投稿作成"| WP
style MD fill:#e8f5e9
style IMPORT fill:#fff3e0
style WP fill:#e3f2fd
この図のように、3つのレイヤーは連携していますが、それぞれの性質は大きく異なります。
性質の異なる3つのレイヤー
各レイヤーの特性を比較すると、違いが明確になります。
記事コンテンツは週1回程度の更新があり、変更頻度が高いです。インポートスクリプトは機能改善時のみ変更され、頻度は低いです。WordPressテーマはデザイン改善時に変更され、中程度の頻度です。
変更の性質も異なります。記事はコンテンツの追加、スクリプトはロジックの変更、テーマはUI/UXの変更です。
この違いが、CI/CDの設計を複雑にしていました。
悩みの構造を整理する
根本的な3つの問い
悩みを整理すると、3つの問いに集約されました。
以下の図は、悩みの構造を示しています。
flowchart TD
subgraph 問い1["コンテンツとコードの管理"]
Q1["記事をGit管理すべきか"]
Q1 --> A1["管理したいが
記事追加でCI/CDが動くのは違和感"]
end
subgraph 問い2["デプロイの定義"]
Q2["デプロイとは何を指すか"]
Q2 --> A2["テーマ反映?
MDファイル配置?
インポート実行?"]
end
subgraph 問い3["インポートの実行"]
Q3["いつ・どこで実行するか"]
Q3 --> A3["ローカル?
本番サーバー?
CI環境?"]
end
問い1 --> 問い2 --> 問い3
この図のように、各問いは連鎖しています。
1つ目の問いは、コンテンツとコードを一緒に管理すべきかという点です。記事もGitで管理したいものの、記事追加のたびにCI/CDが動くのは違和感があります。
2つ目の問いは、「デプロイ」という言葉が複数の意味を持っている点です。テーマの反映、MDファイルの配置、インポートスクリプトの実行、WordPressへの記事表示。どれも「デプロイ」と呼べてしまいます。
3つ目の問いは、インポートスクリプトをいつ・どこで実行するかという点です。ローカル、本番サーバー、CI環境、それぞれにトレードオフがあります。
混乱の根本原因
悩みの根本原因を分析すると、以下の3点に集約されました。
graph TD
ROOT["根本原因"]
ROOT --> C1["3つのレイヤーが
異なるライフサイクルを持つ"]
ROOT --> C2["1リポジトリ = 1デプロイ単位
という暗黙の前提"]
ROOT --> C3["インポートスクリプトの
実行環境が未定義"]
C1 --> PAIN["CI/CD設計の混乱"]
C2 --> PAIN
C3 --> PAIN
この図のように、3つの原因が絡み合って混乱を生んでいました。
異なるライフサイクルを持つレイヤーを1つのリポジトリで管理し、「1リポジトリ = 1デプロイ単位」という前提で考えていたことが問題でした。
リポジトリ分割という解決策
責務分離の基本方針
解決策として、リポジトリを3つに分割することを決めました。基本方針は「各コンポーネントが単一の役割に徹する」ことです。
wpリポジトリは本番システムを担当します。contentリポジトリはコンテンツデータを保管します。importerリポジトリは処理ロジックを担当します。
分割後の構造
分割後の全体像は以下のようになります。
graph TB
subgraph "GitHub"
WP["wp リポジトリ"]
CONTENT["content リポジトリ"]
IMPORTER["importer リポジトリ"]
end
subgraph "本番 EC2"
DOCKER["Docker"]
WORDPRESS["WordPress"]
REST["REST API"]
end
subgraph "ローカルPC"
LOCAL_CONTENT["content clone"]
LOCAL_IMPORTER["importer clone"]
PROCESS["インポート処理"]
end
WP -->|"GitHub Actions"| DOCKER
CONTENT --> LOCAL_CONTENT
IMPORTER --> LOCAL_IMPORTER
LOCAL_CONTENT --> PROCESS
LOCAL_IMPORTER --> PROCESS
PROCESS -->|"REST API"| REST
REST --> WORDPRESS
この図のように、各リポジトリの責務が明確になりました。
wpリポジトリはWordPressテーマとDocker構成を保持し、GitHub Actionsで本番EC2にデプロイします。記事データは一切含みません。
contentリポジトリはMarkdown記事のみを保持します。スクリプトや設定ファイルは含みません。
importerリポジトリは記事のインポート処理を担当します。変換ロジックやAPI連携の実装を含み、ローカルPCでのみ実行します。
CI/CDと自動化の設計
自動化の範囲を明確にする
すべてを自動化するのではなく、適切な範囲を定めました。
テーマのデプロイはwpリポジトリへのpushをトリガーに自動化します。Dockerの再起動もdocker/ディレクトリの変更時に自動化します。
一方、contentのpullとimporterの実行は手動としました。インポート処理が実行されると、REST API経由での投稿は自動的に行われます。
処理フローの設計
システム更新(テーマ・Docker)の処理フローは以下のようになります。
sequenceDiagram
participant DEV as 開発者
participant GH as GitHub (wp)
participant GA as GitHub Actions
participant EC2 as AWS EC2
DEV->>GH: git push
GH->>GA: push event
GA->>EC2: SSH接続
GA->>EC2: git pull
GA->>EC2: npm run build
GA->>EC2: docker compose restart
EC2-->>DEV: デプロイ完了
この図のように、wpリポジトリへのpushをトリガーに自動デプロイが実行されます。
記事公開の処理フローは以下のようになります。
sequenceDiagram
participant DEV as 開発者
participant LOCAL as ローカルPC
participant IMP as importer
participant EC2 as WordPress
DEV->>LOCAL: git pull (content)
DEV->>IMP: インポート実行
IMP->>IMP: Markdown解析
IMP->>IMP: コンテンツ処理
IMP->>EC2: REST API
EC2-->>DEV: 公開完了
この図のように、記事公開は手動でcontentをpullし、importerを実行する流れになります。
意図的に自動化しない理由
完全自動化を目指さなかった理由があります。
graph LR
AUTO["完全自動化"] --> COMPLEX["複雑なCI/CD"]
COMPLEX --> DEBUG["デバッグ困難"]
PARTIAL["部分自動化"] --> SIMPLE["シンプルな構成"]
SIMPLE --> CONTROL["制御しやすい"]
SIMPLE --> UNDERSTAND["理解しやすい"]
style PARTIAL fill:#c8e6c9
style SIMPLE fill:#c8e6c9
この図のように、部分自動化にはメリットがあります。
記事公開は週1回程度であり、手動実行のコストは低いです。高度な処理の実行タイミングを自分で制御できます。トラブル時の原因特定も容易になります。
過度な自動化は複雑さを生み、デバッグを困難にします。シンプルな構成を保つことで、運用の理解しやすさを優先しました。
終わりに
モノレポの課題を整理し、リポジトリを3つに分割する設計判断の過程をご紹介しました。
責務分離の考え方は、コードの設計だけでなく、リポジトリや運用の設計にも適用できます。異なるライフサイクルを持つコンポーネントを無理に1つにまとめず、それぞれの責務を明確にすることで、運用がシンプルになりました。
完全自動化を目指すのではなく、適切な範囲で自動化することも重要な設計判断です。同じような悩みを抱えている方の参考になれば幸いです。
コメントを残す