サイトアイコンmaita tomoya dev io

#19 フロントエンド・バックエンド分離からモノレポへ:AI駆動開発時代の最適解

フロントエンド・バックエンド分離からモノレポへ:AI駆動開発時代の最適解

はじめに

最近、教材共有プラットフォームの開発プロジェクトで、フロントエンド(FE)とバックエンド(BE)を別々のディレクトリで管理していた構成を、**モノレポ(Monorepo)**に統合する作業を行いました。

この記事では、なぜ統合したのか、どのように実施したのか、そしてAI駆動開発時代において、この選択がなぜ重要なのかを詳しく解説します。

プロジェクトの初期状態

分離構成の問題点

project-root/
├── backend/          # Expressサーバー
│   ├── package.json  # 227MBのnode_modules
│   └── src/
└── frontend/         # Reactアプリ
    ├── package.json  # 541MBのnode_modules
    └── src/

合計768MBものnode_modulesが存在し、以下の問題がありました:

  1. 型定義の重複 - material.tsのような共通型が両方に存在
  2. 依存関係の不整合 - バージョンの違いによる予期せぬバグ
  3. 開発効率の低下 - 2つのターミナルで別々にサーバーを起動
  4. 環境変数の分散 - .envファイルが複数存在

モノレポとマルチレポの比較

モノレポ(Monorepo)とは

モノレポは、複数の関連プロジェクトを1つのGitリポジトリで管理する手法です。Google、Facebook、Microsoftなどの大企業が採用しています。

monorepo/
├── packages/
│   ├── backend/
│   ├── frontend/
│   └── shared/      # 共通コード
├── package.json     # ルート設定
└── pnpm-workspace.yaml

マルチレポ(Multirepo)とは

マルチレポは、各プロジェクトを独立したGitリポジトリで管理する従来の手法です。

github.com/
├── project-backend/
└── project-frontend/

メリット・デメリット比較表

モノレポとマルチレポの特徴比較

型定義の共有

  • モノレポ:✅ 一元管理で整合性保証
  • マルチレポ:❌ 手動でコピーが必要

依存関係管理

  • モノレポ:✅ pnpmで効率的に共有
  • マルチレポ:❌ 重複インストール

開発体験

  • モノレポ:✅ 1コマンドで全体起動
  • マルチレポ:❌ 複数ターミナル必要

CI/CD

  • モノレポ:✅ 統合テストが簡単
  • マルチレポ:❌ 連携が複雑

コードレビュー

  • モノレポ:✅ 全体の変更を把握
  • マルチレポ:❌ 別々にレビュー

リポジトリサイズ

  • モノレポ:❌ 大きくなりやすい
  • マルチレポ:✅ 個別に管理

アクセス制御

  • モノレポ:❌ 全体への権限必要
  • マルチレポ:✅ 細かく制御可能

初期セットアップ

  • モノレポ:❌ やや複雑
  • マルチレポ:✅ シンプル

AI駆動開発時代におけるモノレポの重要性

1. AIが理解しやすいコンテキスト

AI(Claude、GitHub Copilot等)にとって、関連コードが1つのリポジトリにあることは非常に重要です。

// packages/shared/types/material.ts
export interface Material {
  id: string;
  title: string;
  uploadedBy: string;
  version?: string;
}

// AIは両側のコードを同時に理解できる
// packages/backend/src/api/materials.ts
import { Material } from '@shared/types';

// packages/frontend/src/components/MaterialCard.tsx
import { Material } from '@shared/types';

2. AIによる自動リファクタリング

型定義を変更した際、AIが影響範囲を正確に把握して修正提案できます:

// AIへの指示:「Material型にdownloadCountフィールドを追加して、関連箇所を全て更新して」
// → AIはbackend/frontend両方の変更箇所を一度に提案可能

3. 統合テストの自動生成

// AIが生成するE2Eテスト例
describe('教材アップロード', () => {
  it('FEからBEまでの一連の流れをテスト', async () => {
    // AIは全体の流れを理解してテストを生成
    const response = await uploadMaterial(testData);
    expect(response.status).toBe(200);

    const materials = await getMaterials();
    expect(materials).toContainEqual(testData);
  });
});

実装手順

1. 秘匿情報の保護(最重要)

# .envファイルのバックアップ
mkdir -p ~/secure-backup
cp backend/.env ~/secure-backup/backend.env.backup
cp frontend/.env ~/secure-backup/frontend.env.backup

# .env.exampleの作成(秘匿情報を除去)
cat > .env.example << EOF
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/dbname

# API Keys
OPENAI_API_KEY=your-api-key-here

# 注意:実際の値は.envファイルに記載してください
EOF

2. pnpmワークスペースの設定

pnpmは、ハードリンクを使用して依存関係を効率的に管理するパッケージマネージャーです。

# pnpm-workspace.yaml
packages:
  - 'packages/*'

3. ディレクトリ構造の再編成

# packagesディレクトリを作成
mkdir -p packages

# 既存ディレクトリを移動
mv backend packages/
mv frontend packages/

# 共有パッケージを作成
mkdir -p packages/shared/src/types

4. 統合package.jsonの作成

{
  "name": "education-platform",
  "private": true,
  "scripts": {
    "dev": "pnpm run --parallel dev",
    "build": "pnpm run --recursive build",
    "test": "pnpm run --recursive test"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  }
}

5. 共通型定義の統一

// packages/shared/src/types/index.ts
export interface User {
  id: string;
  email: string;
  role: 'teacher' | 'student';
}

export interface Material {
  id: string;
  title: string;
  uploadedBy: User['id'];
  downloads: number;
  likes: number;
  version?: {
    parent?: string;
    description?: string;
  };
}

遭遇した問題と解決方法

問題1: CI/CDの失敗

# エラー:pnpm-lock.yamlが見つからない
Error: Unable to find pnpm-lock.yaml

原因.gitignorepnpm-lock.yamlが含まれていた

解決

# .gitignoreから削除
sed -i '' '/pnpm-lock.yaml/d' .gitignore

# lockファイルをコミット
git add pnpm-lock.yaml
git commit -m "fix: Add pnpm-lock.yaml for CI"

問題2: 環境変数の誤検知

# セキュリティスキャンの誤検知
Warning: Potential secret found in process.env.DATABASE_URL

解決:環境変数の参照は問題ないことを確認し、.gitleaks.tomlで除外設定を追加

問題3: TypeScriptパスの解決

// Cannot find module '@shared/types'

解決:tsconfig.jsonにパスマッピングを追加

{
  "compilerOptions": {
    "paths": {
      "@shared/*": ["../shared/src/*"]
    }
  }
}

セキュリティ上の注意事項

1. 環境変数の管理

# ❌ 絶対にやってはいけないこと
git add .env
git commit -m "Add environment variables"

# ✅ 正しい方法
echo ".env" >> .gitignore
git add .gitignore

2. Git履歴のクリーニング

もし誤って秘匿情報をコミットした場合:

# BFG Repo-Cleanerを使用
bfg --delete-files .env
git reflog expire --expire=now --all
git gc --prune=now --aggressive

3. プライベートリポジトリの設定

# GitHubでプライベートリポジトリに変更
gh repo edit --visibility private

パフォーマンスの改善結果

Before(マルチレポ)

  • node_modules合計: 768MB
  • インストール時間: 3分45秒
  • 起動コマンド: 2つのターミナル

After(モノレポ with pnpm)

  • node_modules合計: 412MB(46%削減)
  • インストール時間: 1分20秒(64%短縮)
  • 起動コマンド: 1コマンド

今後の展望

1. Turborepoの導入

ビルドキャッシュと並列実行でさらなる高速化:

{
  "turbo": {
    "pipeline": {
      "build": {
        "dependsOn": ["^build"],
        "outputs": ["dist/**"]
      }
    }
  }
}

2. AIペアプログラミングの活用

  • GitHub Copilot Workspace:モノレポ全体を理解した提案
  • Claude Projects:プロジェクト全体のコンテキストを保持
  • Cursor IDE:AIファーストの開発環境

3. マイクロフロントエンドへの拡張

packages/
├── backend/
├── frontend-teacher/    # 教員用UI
├── frontend-student/    # 生徒用UI
├── frontend-admin/      # 管理者用UI
└── shared/

まとめ

モノレポへの移行は、単なる技術的な選択ではなく、AI駆動開発時代における戦略的な判断です。

主なメリット

  1. 開発効率の向上 - 型の一元管理とホットリロード
  2. AIとの相性 - コンテキストの統一で精度向上
  3. 保守性の改善 - リファクタリングの安全性

注意点

  1. 秘匿情報の管理 - 環境変数は必ず.gitignoreに
  2. 初期設定の複雑さ - 適切なツール選択が重要
  3. チーム教育 - 新しいワークフローの習得

AI駆動開発が当たり前になる未来において、モノレポはAIと人間が協調して開発する最適な環境を提供します。プロジェクトの規模や特性に応じて、適切な構成を選択することが重要です。

参考資料