# 通知設計書 17-バックエンドサービス障害通知

## 概要

本ドキュメントは、Decap CMS において、バックエンドサービス（GitHub API 等）がダウンしている場合に表示される持続的なエラートースト通知の設計を記述する。

### 本通知の処理概要

バックエンドサービス障害通知は、定期的なバックエンドステータスチェックにおいてバックエンドAPI（GitHub、GitLab等）がダウンしていることを検出した場合に、ユーザーに対してサービス障害情報を持続的に表示するトースト通知である。本通知は自動消去されず、サービスが復旧するまで表示され続ける点が他の通知と大きく異なる。

**業務上の目的・背景**：Decap CMS はGitHubやGitLab等の外部サービスをバックエンドとして利用している。これらのサービスがダウンしている場合、エントリの保存・公開・読み込み等の全ての操作が失敗する可能性がある。ユーザーがサービス障害に気づかずに操作を続行し、データ損失が発生することを防ぐため、持続的な通知で障害状況を明示する必要がある。

**通知の送信タイミング**：`checkBackendStatus` アクション内で `backend.status()` の結果として `status.api.status === false` が返された場合に送信される。ただし、既に同じキー（`ui.toast.onBackendDown`）の通知が存在する場合は重複送信されない。

**通知の受信者**：CMSを使用している全てのアクティブセッションユーザー。定期的なステータスチェックはバックグラウンドで実行されるため、明示的なユーザー操作なしに通知が表示される。

**通知内容の概要**：i18nキー `ui.toast.onBackendDown` に対応するローカライズされたエラーメッセージが表示される。英語では "The backend service is experiencing an outage. See %{details} for more information"、日本語では "バックエンドのシステムが停止しています。%{details}" となる。`%{details}` にはバックエンドのステータスページURLが埋め込まれる。

**期待されるアクション**：ユーザーはステータスページのリンクを確認してサービス復旧を待つことが期待される。サービス障害中はデータの保存・公開操作を控え、ローカルでの編集内容をバックアップしておくことが推奨される。

## 通知種別

アプリ内トースト通知（react-toastify ベース） - 持続表示型

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（Redux dispatch） |
| 優先度 | 最高 |
| リトライ | なし（通知自体のリトライはなし。ステータスチェックは定期的に繰り返される） |

### 送信先決定ロジック

バックグラウンドの定期ステータスチェックにより、全てのアクティブセッションユーザーに対してトースト通知を表示する。重複防止ロジックにより、同じ通知が複数表示されることはない。

## 通知テンプレート

### メール通知の場合

該当なし（本通知はアプリ内トースト通知のみ）

### 本文テンプレート

```
The backend service is experiencing an outage. See {details} for more information
```

日本語の場合：
```
バックエンドのシステムが停止しています。{details}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| details | バックエンドサービスのステータスページURL | `backend.status()` の結果オブジェクト `status.api.statusPage` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バックグラウンドポーリング | 定期的なバックエンドステータスチェック | `status.api.status === false` かつ同キーの既存通知がない場合 | `checkBackendStatus` thunkアクション内で発火 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 既に同キーの通知が存在する場合 | `previousBackendDownNotifications.length > 0` の場合、新たな通知は送信されない（行53）。重複防止ロジック |
| バックエンドAPIが正常な場合 | `status.api.status === true` の場合、通知は送信されない。既存の障害通知がある場合はそれらが `dismissNotification` で自動消去される（行65-70） |
| フェッチ中の場合 | `state.status.isFetching` が true の場合、ステータスチェック自体がスキップされる（行39-41） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[バックグラウンド定期ポーリング] --> B[checkBackendStatus thunk dispatch]
    B --> C{isFetching?}
    C -->|Yes| D[早期リターン]
    C -->|No| E[statusRequest dispatch]
    E --> F[backend.status 呼び出し]
    F --> G{api.status}
    G -->|false| H{既存の障害通知あり?}
    H -->|Yes| I[通知送信スキップ]
    H -->|No| J[addNotification dispatch - onBackendDown]
    J --> K[持続的トースト表示 - 自動消去なし]
    G -->|true| L{既存の障害通知あり?}
    L -->|Yes| M[全障害通知を dismissNotification で消去]
    L -->|No| N[通常フロー続行]
    M --> O[auth ステータスチェック]
    N --> O
    I --> P[statusSuccess dispatch]
    K --> P
    O --> P
    P --> Q[終了]
    D --> Q
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| Redux Store: notifications | 重複通知チェック | `notifications.notifications` から `ui.toast.onBackendDown` キーの既存通知を検索 |
| Redux Store: status | フェッチ中チェック | `status.isFetching` で重複チェック実行を防止 |

### テーブル別参照項目詳細

#### Redux Store: notifications

| 参照項目 | 用途 | 取得条件 |
|---------|------|---------|
| notifications.notifications | 既存の障害通知の存在チェック | `n.message?.key === 'ui.toast.onBackendDown'` でフィルタリング |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Redux Store: notifications | INSERT / DELETE | 障害検知時に通知追加、復旧時に既存障害通知を削除 |
| Redux Store: status | UPDATE | STATUS_REQUEST → STATUS_SUCCESS でステータス情報を更新 |

#### Redux Store 更新詳細

| 操作 | 項目 | 更新値 | 備考 |
|-----|------|-------|------|
| INSERT | notifications.notifications[] | `{ id: uuid(), message: { details: status.api.statusPage, key: 'ui.toast.onBackendDown' }, type: 'error' }` | `dismissAfter` 未設定のため持続表示 |
| DELETE | notifications.notifications[] | 対象通知を配列から除去 | 復旧時に `dismissNotification(notification.id)` で消去 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ステータスチェック自体の失敗 | `backend.status()` が例外をスローした場合 | `statusFailure(error)` を dispatch。通知は表示されない |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 該当なし（ステータスチェック自体が定期ポーリングであるため、次回のチェックが実質的なリトライとなる） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 実質1件（重複防止ロジックにより同時に1件のみ表示） |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（バックグラウンドポーリングにより随時発生）

## セキュリティ考慮事項

- ステータスページURLがトースト通知に表示されるが、これは一般に公開されている情報（GitHub Status等）であるため、セキュリティリスクは低い。
- `status.api.statusPage` の内容がそのまま表示されるため、バックエンド実装がこの値を適切に設定していることが前提となる。

## 備考

- **持続表示型通知**：`dismissAfter` が設定されていないため、トーストは自動消去されない。これにより、サービス障害中は常にユーザーの目に入る位置に通知が表示され続ける。
- **自動復旧時の消去**：サービスが復旧した場合（`status.api.status === true` かつ既存障害通知あり）、`dismissNotification` で全ての障害通知が自動消去される（行65-70）。
- **重複防止**：同じキーの通知が既に存在する場合は新たな通知を送信しない。これにより、ポーリングのたびに通知が増殖することを防ぐ。
- 本通知の i18nキーは `ui.toast.onBackendDown` であり、多言語対応済みである。

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | `NotificationPayload` と `NotificationMessage` の型を理解する。本通知では `dismissAfter` が未設定である点に注目 |
| 1-2 | status.ts | `packages/decap-cms-core/src/actions/status.ts` | `statusSuccess` のペイロード型（行18-21）で `status.auth.status` と `status.api.status` および `status.api.statusPage` の構造を理解する |

**読解のコツ**: 本通知の最大の特徴は `dismissAfter` が未設定であること（持続表示）と、重複防止ロジック、およびサービス復旧時の自動消去である。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | status.ts | `packages/decap-cms-core/src/actions/status.ts` | `checkBackendStatus` 関数（行35-95）がステータスチェックのエントリーポイント |

**主要処理フロー**:
1. **行39-41**: `isFetching` ガードによる重複チェック防止
2. **行43**: `statusRequest` を dispatch
3. **行45**: `backend.status()` でバックエンドステータスを取得
4. **行47-49**: 既存の `onBackendDown` 通知を検索（重複防止用）
5. **行52-63**: APIダウン時の処理
   - **行53**: 既存通知がない場合のみ `addNotification` を dispatch（**本通知の発火箇所**）
6. **行65-70**: API復旧時の処理 - 既存障害通知を全て `dismissNotification` で消去
7. **行72-88**: 認証ステータスチェック（No.18 セッション切断通知に関連）
8. **行90**: `statusSuccess` を dispatch

#### Step 3: 重複防止ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | status.ts | `packages/decap-cms-core/src/actions/status.ts` | 行47-49: `state.notifications.notifications.filter()` で既存通知を検索。`n.message?.key === backendDownKey` で同じi18nキーの通知を検出 |
| 3-2 | status.ts | `packages/decap-cms-core/src/actions/status.ts` | 行53: `previousBackendDownNotifications.length === 0` の場合のみ新規通知を送信 |

#### Step 4: 復旧時の自動消去を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | status.ts | `packages/decap-cms-core/src/actions/status.ts` | 行65-70: `status.api.status === true` かつ既存障害通知がある場合、`forEach` で全通知に対して `dismissNotification` を dispatch |
| 4-2 | notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | `dismissNotification` 関数（行22-23）が `NOTIFICATION_DISMISS` アクションを生成 |
| 4-3 | notifications.ts (reducers) | `packages/decap-cms-core/src/reducers/notifications.ts` | `NOTIFICATION_DISMISS` ケース（行37-38）で対象通知を配列から除去 |

#### Step 5: UIレンダリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | Notifications.tsx | `packages/decap-cms-core/src/components/UI/Notifications.tsx` | 行39: `autoClose: notification.dismissAfter` が `undefined` の場合、react-toastify はトーストを自動消去しない（持続表示）。行54-60: `dismissNotification` により通知が消えた場合、`toast.dismiss(toastId)` でUIからも消去 |

#### Step 6: i18nメッセージを確認する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | index.js (en) | `packages/decap-cms-locales/src/en/index.js` | 行284-285: `onBackendDown: 'The backend service is experiencing an outage. See %{details} for more information'` |
| 6-2 | index.js (ja) | `packages/decap-cms-locales/src/ja/index.js` | 行271: `onBackendDown: 'バックエンドのシステムが停止しています。%{details}'` |

### プログラム呼び出し階層図

```
checkBackendStatus (status.ts:35)
    |
    +-- statusRequest (status.ts:12)
    |
    +-- backend.status() (バックエンドAPI: ステータス取得)
    |
    +-- [APIダウン時]
    |       |
    |       +-- 既存通知チェック (status.ts:47-49)
    |       |
    |       +-- [通知なし] addNotification (notifications.ts:18)
    |       |       |
    |       |       +-- NOTIFICATION_SEND → reducer (reducers/notifications.ts:40)
    |       |               |
    |       |               +-- Notifications.tsx useEffect → toast() (持続表示)
    |       |
    |       +-- statusSuccess (status.ts:18)
    |
    +-- [API復旧時 + 既存通知あり]
    |       |
    |       +-- dismissNotification (notifications.ts:22) × N回
    |       |       |
    |       |       +-- NOTIFICATION_DISMISS → reducer (reducers/notifications.ts:37)
    |       |               |
    |       |               +-- Notifications.tsx → toast.dismiss()
    |       |
    |       +-- 認証チェックへ続行
    |
    +-- statusSuccess (status.ts:18)
```

### データフロー図

```
[入力]                           [処理]                              [出力]

バックグラウンド ────────> checkBackendStatus thunk ────────> Redux dispatch
ポーリング                       |                                    |
                         backend.status()                             |
                                 |                            [APIダウン時]
                         status = {                           NOTIFICATION_SEND
                           api: {                              (dismissAfter なし)
                             status: false,                          |
                             statusPage: 'https://...'        reducer: notifications
                           },                                        |
                           auth: { status: true }             Notifications コンポーネント
                         }                                           |
                                 |                            react-toastify toast()
                         addNotification({                           |
                           message: {                         持続的トースト表示
                             details: statusPage,
                             key: 'ui.toast.                  [API復旧時]
                               onBackendDown'                 NOTIFICATION_DISMISS
                           },                                        |
                           type: 'error'                      toast.dismiss()
                         })                                          |
                                                              トースト自動消去
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| status.ts | `packages/decap-cms-core/src/actions/status.ts` | ソース | バックエンドステータスチェックのthunkアクション。障害検知時に通知を dispatch し、復旧時に通知を消去する |
| notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | ソース | `addNotification` と `dismissNotification` アクションクリエーター |
| notifications.ts (reducers) | `packages/decap-cms-core/src/reducers/notifications.ts` | ソース | 通知ステートの管理リデューサー（追加・消去） |
| Notifications.tsx | `packages/decap-cms-core/src/components/UI/Notifications.tsx` | ソース | トースト通知のUIコンポーネント（持続表示と自動消去の両方をサポート） |
| index.js (en) | `packages/decap-cms-locales/src/en/index.js` | i18n | 英語翻訳定義 |
| index.js (ja) | `packages/decap-cms-locales/src/ja/index.js` | i18n | 日本語翻訳定義 |
