# アーキテクチャ設計書

## 概要

本ドキュメントは、Decap CMS（旧 Netlify CMS）のアプリケーションアーキテクチャを定義する設計書である。Decap CMS は静的サイトジェネレータ向けの Git ベース React CMS であり、Lerna + Nx によるモノレポ構成で 38 個の独立パッケージから構成される。Redux による状態管理、プラグインレジストリパターンによる拡張性、複数 Git プロバイダーとの連携を特徴とする。

## システム全体構成

### アーキテクチャ概要図

[アーキテクチャ構成図](./アーキテクチャ構成図.md) を参照。

### システム境界と外部連携

| 外部システム | 連携方式 | 用途 |
| --- | --- | --- |
| GitHub | REST API / GraphQL API（OAuth 認証） | Git リポジトリへのコンテンツ CRUD、Editorial Workflow（PR ベース） |
| GitLab | REST API（OAuth 認証） | Git リポジトリへのコンテンツ CRUD |
| Bitbucket | REST API（OAuth 認証） | Git リポジトリへのコンテンツ CRUD |
| Azure DevOps | REST API（OAuth 認証） | Azure Repos へのコンテンツ CRUD |
| Gitea | REST API（OAuth 認証） | Gitea サーバーへのコンテンツ CRUD |
| Netlify Git Gateway | REST API（GoTrue JWT 認証） | Netlify 経由での Git プロバイダーアクセス仲介 |
| Decap CMS Proxy Server | REST API（ローカル） | ローカル開発時のファイルシステム直接アクセス |
| Cloudinary | JavaScript SDK（Media Library Widget） | 外部メディアライブラリ統合（画像・動画管理） |
| Uploadcare | JavaScript SDK（Upload Widget） | 外部メディアライブラリ統合（ファイルアップロード） |
| AWS Cognito | Cognito SDK（OAuth プロキシ） | GitHub 認証の AWS Cognito 経由プロキシ |

## レイヤー構成

### アーキテクチャスタイル

**プラグインベースのレイヤードアーキテクチャ + Flux パターン（Redux）**

Decap CMS は、以下のアーキテクチャスタイルを組み合わせている：

1. **レイヤードアーキテクチャ**: Presentation → Application（Redux Actions/Thunks） → Domain（Reducers/ValueObjects） → Infrastructure（Backend/API）の層構造
2. **Flux パターン（Redux）**: 単方向データフローによる状態管理（Action → Reducer → Store → View）
3. **プラグインアーキテクチャ**: Registry パターンによるバックエンド・ウィジェット・メディアライブラリの差し替え可能設計
4. **モノレポパッケージ分割**: 機能ドメインごとに独立した npm パッケージとして分離

### レイヤー定義

| レイヤー | 責務 | 主なコンポーネント |
| --- | --- | --- |
| Presentation | ユーザーインターフェース、React コンポーネント、ルーティング | `components/App/`, `components/Collection/`, `components/Editor/`, `components/MediaLibrary/`, `components/Workflow/`, `decap-cms-ui-default`, `decap-cms-ui-auth` |
| Application | ビジネスロジックの調整、非同期処理、Redux Actions/Thunks | `actions/auth.ts`, `actions/config.ts`, `actions/entries.ts`, `actions/editorialWorkflow.ts`, `actions/mediaLibrary.ts`, `actions/media.ts`, `actions/status.ts` |
| Domain | アプリケーション状態の管理、ドメインモデル、Redux Reducers | `reducers/auth.ts`, `reducers/config.ts`, `reducers/entries.ts`, `reducers/entryDraft.js`, `reducers/editorialWorkflow.ts`, `valueObjects/AssetProxy.ts`, `formats/` |
| Infrastructure | 外部サービスとの通信、Git API クライアント、認証プロバイダー | `backend.ts`, `decap-cms-backend-*`（9 パッケージ）, `decap-cms-lib-auth`, `decap-cms-media-library-*` |

### レイヤー間の依存関係ルール

- **Presentation → Application**: コンポーネントは Redux の `connect()` または `useSelector`/`useDispatch` を通じて Action を dispatch する
- **Application → Domain**: Actions/Thunks は Reducer を通じて状態を更新し、`getState()` で現在の状態を参照する
- **Application → Infrastructure**: Thunks はバックエンド API を直接呼び出してデータの取得・永続化を行う
- **Infrastructure → Domain**: バックエンドからの応答データは ValueObject（`AssetProxy` 等）やフォーマッター（`formats/`）を通じてドメインモデルに変換される
- **禁止される参照**: Presentation が Infrastructure を直接参照することは禁止。必ず Application レイヤー（Redux Actions）を経由する
- **プラグインの分離**: 各バックエンド・ウィジェットパッケージは `Implementation` インターフェースを実装し、Registry を通じて Core に登録される。Core がプラグインの内部実装を直接参照することはない

## モジュール構成

### ドメイン分割

| ドメイン | 責務 | 関連モジュール |
| --- | --- | --- |
| Core | アプリケーションの中心ロジック、Redux Store、コンポーネント基盤、プラグインレジストリ | `decap-cms-core` |
| App | プラグイン統合・初期化、全バックエンド・ウィジェット・ロケールの登録 | `decap-cms-app` |
| Entry Point | 最終エントリーポイント、自動初期化、グローバルスコープ公開 | `decap-cms` |
| Backend | Git プロバイダーとの通信、認証フロー、コンテンツ CRUD | `decap-cms-backend-github`, `decap-cms-backend-gitlab`, `decap-cms-backend-bitbucket`, `decap-cms-backend-azure`, `decap-cms-backend-gitea`, `decap-cms-backend-git-gateway`, `decap-cms-backend-proxy`, `decap-cms-backend-aws-cognito-github-proxy`, `decap-cms-backend-test` |
| Widget | コンテンツフィールドの入力・プレビューコンポーネント | `decap-cms-widget-string`, `decap-cms-widget-number`, `decap-cms-widget-markdown`, `decap-cms-widget-image`, `decap-cms-widget-file`, `decap-cms-widget-boolean`, `decap-cms-widget-select`, `decap-cms-widget-datetime`, `decap-cms-widget-list`, `decap-cms-widget-object`, `decap-cms-widget-relation`, `decap-cms-widget-text`, `decap-cms-widget-code`, `decap-cms-widget-map`, `decap-cms-widget-colorstring` |
| UI | 共有 UI コンポーネント、認証画面 | `decap-cms-ui-default`, `decap-cms-ui-auth` |
| Auth | 認証ライブラリ（OAuth PKCE, Implicit, Netlify Auth） | `decap-cms-lib-auth` |
| Utility | 共有ユーティリティ、テンプレート処理、バリデーション | `decap-cms-lib-util`, `decap-cms-lib-widgets` |
| Media | 外部メディアライブラリ統合 | `decap-cms-media-library-cloudinary`, `decap-cms-media-library-uploadcare` |
| Localization | 多言語対応（i18n） | `decap-cms-locales` |
| Server | ローカル開発用プロキシサーバー | `decap-server` |

### パッケージ構造

```
decap-cms-main/
├── packages/                          # モノレポパッケージ群（38個）
│   ├── decap-cms/                     # エントリーポイントパッケージ
│   │   └── src/
│   │       ├── index.js               # 自動初期化 + window.CMS 公開
│   │       └── extensions.js          # メディアライブラリ登録
│   ├── decap-cms-app/                 # アプリケーション統合パッケージ
│   │   └── src/
│   │       ├── index.js               # エクスポート
│   │       ├── extensions.js          # 全プラグイン登録
│   │       └── locales.js             # ロケールインポート
│   ├── decap-cms-core/                # コアロジックパッケージ
│   │   └── src/
│   │       ├── actions/               # Redux Action Creators
│   │       │   ├── auth.ts            # 認証アクション
│   │       │   ├── config.ts          # 設定読み込み・正規化
│   │       │   ├── entries.ts         # エントリ CRUD
│   │       │   ├── editorialWorkflow.ts # ワークフロー管理
│   │       │   ├── mediaLibrary.ts    # メディアライブラリ
│   │       │   ├── media.ts           # メディアアセット
│   │       │   ├── search.ts          # 検索
│   │       │   └── status.ts          # バックエンドステータス
│   │       ├── components/            # React コンポーネント
│   │       │   ├── App/               # ルートアプリケーション
│   │       │   ├── Collection/        # コレクション一覧・表示
│   │       │   ├── Editor/            # エントリエディタ
│   │       │   ├── EditorWidgets/     # ウィジェットレジストリ UI
│   │       │   ├── MediaLibrary/      # メディアライブラリ UI
│   │       │   ├── Workflow/          # 編集ワークフロー UI
│   │       │   └── UI/               # 汎用 UI（ErrorBoundary 等）
│   │       ├── reducers/              # Redux Reducers
│   │       │   ├── combinedReducer.ts # ルート結合リデューサー
│   │       │   ├── auth.ts            # 認証状態
│   │       │   ├── config.ts          # 設定状態
│   │       │   ├── entries.ts         # エントリデータ
│   │       │   ├── entryDraft.js      # ドラフト編集状態
│   │       │   ├── editorialWorkflow.ts # ワークフロー状態
│   │       │   ├── medias.ts          # メディアアセット状態
│   │       │   ├── mediaLibrary.ts    # メディアライブラリ状態
│   │       │   ├── notifications.ts   # 通知状態
│   │       │   └── globalUI.ts        # グローバル UI 状態
│   │       ├── redux/                 # Redux Store 設定
│   │       │   ├── index.ts           # Store 作成・Middleware 設定
│   │       │   └── middleware/
│   │       │       └── waitUntilAction.ts # カスタム Middleware
│   │       ├── routing/               # ルーティング
│   │       │   └── history.ts         # Hash History + ナビゲーション関数
│   │       ├── lib/                   # ユーティリティ
│   │       │   ├── registry.js        # プラグインレジストリ（グローバル）
│   │       │   ├── formatters.ts      # データフォーマッター
│   │       │   ├── i18n.ts            # 多言語サポート
│   │       │   └── consoleError.js    # エラーログユーティリティ
│   │       ├── formats/               # ファイルフォーマット処理
│   │       │   ├── yaml.ts            # YAML パーサー
│   │       │   ├── toml.ts            # TOML パーサー
│   │       │   ├── json.ts            # JSON パーサー
│   │       │   ├── frontmatter.ts     # Frontmatter パーサー
│   │       │   └── formats.ts         # フォーマット統合
│   │       ├── valueObjects/          # ドメインオブジェクト
│   │       │   └── AssetProxy.ts      # メディアアセットプロキシ
│   │       ├── types/                 # TypeScript 型定義
│   │       ├── backend.ts             # バックエンド抽象化層
│   │       ├── bootstrap.js           # アプリケーション初期化
│   │       └── index.js               # エクスポート
│   ├── decap-cms-backend-github/      # GitHub バックエンド
│   │   └── src/
│   │       ├── implementation.tsx     # Backend インターフェース実装
│   │       ├── API.ts                 # REST API クライアント
│   │       └── GraphQLAPI.ts          # GraphQL API クライアント
│   ├── decap-cms-backend-gitlab/      # GitLab バックエンド
│   ├── decap-cms-backend-bitbucket/   # Bitbucket バックエンド
│   ├── decap-cms-backend-azure/       # Azure DevOps バックエンド
│   ├── decap-cms-backend-gitea/       # Gitea バックエンド
│   ├── decap-cms-backend-git-gateway/ # Netlify Git Gateway バックエンド
│   ├── decap-cms-backend-proxy/       # プロキシバックエンド
│   ├── decap-cms-backend-aws-cognito-github-proxy/ # AWS Cognito プロキシ
│   ├── decap-cms-backend-test/        # テスト用ダミーバックエンド
│   ├── decap-cms-widget-*/            # ウィジェットパッケージ（15個）
│   ├── decap-cms-ui-default/          # デフォルト UI コンポーネント
│   ├── decap-cms-ui-auth/             # 認証 UI
│   ├── decap-cms-lib-auth/            # 認証ライブラリ
│   ├── decap-cms-lib-util/            # 共有ユーティリティ
│   ├── decap-cms-lib-widgets/         # ウィジェット共有ライブラリ
│   ├── decap-cms-locales/             # 多言語リソース
│   ├── decap-cms-media-library-cloudinary/  # Cloudinary 統合
│   ├── decap-cms-media-library-uploadcare/  # Uploadcare 統合
│   └── decap-server/                  # ローカル開発用プロキシサーバー
├── scripts/                           # ビルドスクリプト
│   └── webpack.js                     # Webpack 共通設定
├── cypress/                           # E2E テスト
├── dev-test/                          # ローカル開発テスト環境
├── __mocks__/                         # Jest モック
└── docs/                              # ドキュメント
```

### コンポーネント依存関係

依存関係は以下の階層構造をとる：

1. **decap-cms** → **decap-cms-app** → **decap-cms-core** の 3 層のパッケージ階層
2. **decap-cms-app** が全てのバックエンド・ウィジェット・メディアライブラリパッケージを `extensions.js` で統合
3. **decap-cms-core** は具体的なバックエンド・ウィジェット実装に依存せず、Registry パターンを通じて実行時に解決
4. 各 **decap-cms-backend-*** パッケージは `decap-cms-lib-auth`（認証）と `decap-cms-lib-util`（ユーティリティ）に依存
5. 各 **decap-cms-widget-*** パッケージは `decap-cms-ui-default`（UI コンポーネント）と `decap-cms-lib-widgets`（バリデーション等）に依存

## ミドルウェア構成

### データベース

| 種類 | ミドルウェア | バージョン | 用途 |
| --- | --- | --- | --- |
| ブラウザストレージ | localForage | ランタイム依存 | エントリドラフトのバックアップ保存、エラー時の復旧用データ永続化 |
| ブラウザストレージ | Session Storage | Web API | PKCE 認証のコード検証器保管 |

※ Decap CMS はクライアントサイドアプリケーションであり、サーバーサイドのデータベースは使用しない。データの永続化は Git リポジトリを通じて行われる。

### キャッシュ

| ミドルウェア | バージョン | 用途 | TTL |
| --- | --- | --- | --- |
| Nx Build Cache | ^21.1.0 | ビルド結果のキャッシング（開発時） | ビルド設定変更まで |
| ブラウザキャッシュ | Web API | API レスポンスキャッシュ（ETag ベース） | Git プロバイダー依存 |

### メッセージキュー

該当なし。Decap CMS はクライアントサイド SPA であり、メッセージキューは使用しない。

### 検索エンジン

| ミドルウェア | バージョン | 用途 |
| --- | --- | --- |
| Algolia（オプション） | Integrations 設定による | コレクションの検索・フィルタリング（外部統合として設定可能） |

### その他ミドルウェア

| ミドルウェア | バージョン | 用途 |
| --- | --- | --- |
| Git LFS | バックエンド依存 | 大容量ファイル（画像・動画）のストレージ管理（Git Gateway バックエンド使用時） |
| GoTrue | decap-cms-lib-auth 内蔵 | Netlify Identity による JWT ベース認証 |

## データフロー

### リクエスト処理の流れ

**エントリ取得（同期リクエスト）の処理フロー：**

1. React コンポーネント（`Collection` or `Editor`）が Redux Action を dispatch する
2. `actions/entries.ts` の Thunk が実行され、`getState()` で現在の認証・設定情報を取得する
3. `backend.ts` の抽象化層を通じて、登録済みのバックエンド実装（例：`decap-cms-backend-github`）の API クライアントが呼び出される
4. バックエンドは Git プロバイダー（GitHub API 等）に HTTP リクエストを送信し、ファイルコンテンツを取得する
5. 取得されたファイルコンテンツは `formats/`（YAML/TOML/JSON パーサー）でパースされ、構造化データに変換される
6. Redux Reducer（`reducers/entries.ts`）が状態を更新し、React コンポーネントが再レンダリングされる

**エントリ保存の処理フロー：**

1. エディタコンポーネントから「Save」アクションが dispatch される
2. `actions/entries.ts` の `persistEntry()` Thunk が実行される
3. `entryDraft` Reducer から現在の編集中データを取得し、`formats/` でファイル形式にシリアライズする
4. メディアアセット（`AssetProxy`）がある場合、Base64 変換してバックエンドにアップロードする
5. バックエンドの `persistEntry()` メソッドが Git プロバイダー API を通じてファイルをコミットする
6. Editorial Workflow 有効時は、ブランチ作成 → コミット → PR 作成の流れで処理される
7. 保存完了後、`entries` Reducer が更新され、通知（`notifications`）が表示される

### 非同期処理の流れ

**認証フロー（OAuth）：**

1. ユーザーが認証画面で「Login」ボタンをクリックする
2. `actions/auth.ts` の `loginUser()` Thunk が dispatch される
3. バックエンド実装の `authenticate()` が呼び出され、OAuth フローが開始される
4. `decap-cms-lib-auth` の認証プロバイダー（PKCE / Implicit / Netlify Auth）がポップアップまたはリダイレクトで認証画面を表示する
5. 認証成功時、トークンを受け取り `AUTH_SUCCESS` アクションで状態を更新する
6. `waitUntilAction` Middleware が `AUTH_SUCCESS` を検知し、保留中の処理（設定読み込み等）を実行する

**バックエンドステータス監視：**

1. 定期的に `actions/status.ts` の `checkBackendStatus()` が dispatch される
2. バックエンドの `status()` API を呼び出し、API の可用性と認証状態を確認する
3. API ダウン時や認証切れ時に `notifications` Reducer を通じてトースト通知を表示する

### データ永続化の流れ

- **Git リポジトリ（プライマリ）**: 全てのコンテンツデータ（エントリ、メディアファイル）は Git リポジトリにコミットとして永続化される。各バックエンド実装が Git プロバイダー API を通じて読み書きを行う
- **ブラウザ localForage（一時保存）**: エントリ編集中のドラフトデータは localForage に自動バックアップされ、ブラウザクラッシュやエラー時のデータ復旧に使用される
- **ブラウザ Session Storage（認証情報）**: PKCE 認証のコード検証器やセッショントークンが一時保管される

## 横断的関心事

### 認証・認可

| 方式 | 実装箇所 | 対象 |
| --- | --- | --- |
| PKCE OAuth 2.0 | `decap-cms-lib-auth/src/pkce-oauth.js` | GitHub, GitLab, Azure 等の OAuth 対応プロバイダー（推奨方式） |
| Implicit OAuth | `decap-cms-lib-auth/src/implicit-oauth.js` | レガシー互換用の OAuth フロー |
| Netlify Auth（GoTrue） | `decap-cms-lib-auth/src/netlify-auth.js` | Netlify Identity 経由の認証（ポップアップ方式） |
| Open Authoring | `actions/auth.ts` (`useOpenAuthoring`) | GitHub フォークベースの匿名ユーザー編集（PR ワークフロー） |

### ロギング・監査

| 種類 | 実装方式 | 保存先 |
| --- | --- | --- |
| アプリケーションログ | `console.log()` / `console.error()` / `console.warn()` | ブラウザコンソール |
| 起動ログ | `bootstrap.js` 内のバージョン表示・設定 URL 表示 | ブラウザコンソール |
| 警告ログ | `actions/config.ts` の deprecated 設定キー検出、`lib/registry.js` の重複登録検出 | ブラウザコンソール |
| エラーログ | `lib/consoleError.js` のスタイル付きエラー出力 | ブラウザコンソール |

※ Decap CMS はクライアントサイドアプリケーションのため、サーバーサイドの構造化ログやアクセスログ、監査ログは存在しない。

### エラーハンドリング

| エラー種別 | ハンドリング方式 | レスポンス |
| --- | --- | --- |
| React レンダリングエラー | `ErrorBoundary` コンポーネント（`components/UI/ErrorBoundary.js`） | エラー画面表示 + GitHub Issue テンプレート生成 + localForage からのバックアップ復旧 |
| 認証エラー | `actions/auth.ts` の catch ブロック → Redux notification dispatch | トースト通知（`ui.toast.onFailToAuth`、8 秒後自動消去） |
| バックエンド API エラー | 各バックエンド実装の catch ブロック → Action 内でエラー通知 | トースト通知（エラー種別に応じた i18n メッセージ） |
| 設定エラー | `actions/config.ts` の `validateConfig()` → `CONFIG_FAILURE` dispatch | 設定読み込み失敗画面表示 |
| バックエンドダウン | `actions/status.ts` の定期監視 → `notifications` 経由通知 | トースト通知（`ui.toast.onBackendDown` + ステータスページ URL） |
| コンソールエラー | `lib/consoleError.js` のスタイル付き出力 | ブラウザコンソールに視覚的なエラーメッセージ |

### トランザクション管理

| 範囲 | 管理方式 | 分離レベル |
| --- | --- | --- |
| Git コミット | バックエンド API のアトミックコミット | Git プロバイダーの API レベルで保証（単一コミット = 単一トランザクション） |
| Redux State | Redux の同期的 dispatch（Reducer は純粋関数） | 単一の dispatch で状態が一貫して更新される |
| Editorial Workflow | ブランチ + PR ベースの楽観的ロック | ブランチ分離により並行編集を許容、マージ時に競合検出 |
| 並行ダウンロード | `AsyncLock` + Semaphore（`MAX_CONCURRENT_DOWNLOADS = 10`） | バックエンドへの同時リクエスト数を制限し、リソース枯渇を防止 |

## 設計原則・コーディング規約

### 適用している設計原則

| 原則 | 適用箇所 | 実装例 |
| --- | --- | --- |
| プラグインアーキテクチャ（Registry パターン） | `lib/registry.js` | `CMS.registerBackend()`, `CMS.registerWidget()`, `CMS.registerMediaLibrary()` で実行時にプラグインを登録。Core は具体的な実装に依存しない |
| 単方向データフロー（Flux/Redux） | `redux/`, `actions/`, `reducers/` | Action → Reducer → Store → View の一方向フローで状態を管理。副作用は Redux Thunk で分離 |
| インターフェース分離（Strategy パターン） | `backend.ts` + 各バックエンド実装 | `Implementation` インターフェースを定義し、GitHub/GitLab/Bitbucket 等の具体実装を差し替え可能にする |
| 不変性（Immutability） | `reducers/` + Immer | Redux Reducer は `produce()`（Immer）を使用して不変更新を実現。一部の Reducer では `Immutable.js` を使用 |
| 関心の分離（Separation of Concerns） | パッケージ分割 | バックエンド・ウィジェット・UI・認証・ユーティリティを独立パッケージとして分離し、単独での開発・テスト・公開を可能にする |
| コンポジション | `decap-cms-app/src/extensions.js` | 必要なプラグインを選択的に組み合わせてアプリケーションを構成する |

### コーディング規約

- **ESLint**: `eslint:recommended` + `plugin:react/recommended` + `prettier` ベース。`@emotion` プラグインで CSS-in-JS の規約を強制
- **Prettier**: `singleQuote: true`, `trailingComma: "all"`, `printWidth: 100`, `arrowParens: "avoid"`
- **import 順序**: ESLint `import/order` ルールにより、builtin/external → internal/parent/sibling/index の順で整理し、グループ間に空行を挿入
- **TypeScript**: `strict: true`, `noUnusedLocals: true`, `noUnusedParameters: true`。ただし段階的な TypeScript 移行中であり、`.js` と `.ts`/`.tsx` が混在する
- **Stylelint**: `stylelint-config-standard-scss` + `postcss-styled-syntax` で Emotion CSS-in-JS のスタイルをリント
- **コーディングスタイル**: `object-shorthand: "always"`, `prefer-const: { destructuring: "all" }` を強制

## 備考

- **モノレポ管理**: Lerna（独立バージョン管理）+ Nx（ビルドキャッシュ・タスクオーケストレーション）の組み合わせで 38 パッケージを管理
- **ビルド出力**: 各パッケージは UMD（`dist/${pkg.name}.js`）と ESM（`dist/esm/index.js`）の 2 形式でビルドされ、CDN 配信と npm パッケージの両方に対応
- **テスト戦略**: Jest（ユニットテスト、jsdom 環境）+ Cypress（E2E テスト、`cypress/e2e/`）の 2 層テスト
- **Conventional Commits**: Lerna の `conventionalCommits: true` 設定により、コミットメッセージ規約に基づく自動バージョニングを採用
- **TypeScript 移行**: プロジェクトは段階的に TypeScript へ移行中。Core の主要ファイル（actions, reducers, types）は TypeScript 化済みだが、一部コンポーネントや古いウィジェットは JavaScript のまま残存
- **React 19 対応**: React ^19.1.0 を使用し、`createRoot` API でアプリケーションをマウント
