Skip to content

モノレポを分割した話: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つにまとめず、それぞれの責務を明確にすることで、運用がシンプルになりました。

完全自動化を目指すのではなく、適切な範囲で自動化することも重要な設計判断です。同じような悩みを抱えている方の参考になれば幸いです。

コメントを残す