# バッチ設計書 17-NotifyRecipientsJob

## 概要

本ドキュメントは、受信者通知ジョブ（NotifyRecipientsJob）の設計仕様を記載したものです。

### 本バッチの処理概要

NotifyRecipientsJobは、通知可能なオブジェクト（Event、Mention）に対して、対象ユーザーへの通知レコード（Notification）を作成する非同期ジョブです。

**業務上の目的・背景**：カードへのコメント、アサイン、メンションなどのイベントが発生した際に、関係するユーザーに通知を配信する必要があります。通知作成を非同期で処理することで、ユーザー操作のレスポンスを損なわずに確実に通知を配信できます。

**バッチの実行タイミング**：Notifiable concernを含むモデル（Event、Mention）が作成された後、after_create_commitコールバックによりジョブがキューに投入されます。

**主要な処理内容**：
1. 通知可能なオブジェクト（notifiable）を受け取る
2. Notifier.for(notifiable)で適切なNotifierを取得
3. Notifier#notifyで通知処理を実行
4. 通知対象者ごとにNotificationレコードを作成または更新
5. メールバンドリング設定がある場合はバンドル処理

**前後の処理との関連**：Event/Mentionの作成後に実行されます。Notification作成後、source_idが設定されるとPushNotificationJobが起動します。また、ユーザーのメール設定によってはNotification::Bundle::DeliverJobが後続で実行されます。

**影響範囲**：notificationsテーブルへのレコード作成・更新、notification_bundlesへのレコード追加。

## バッチ種別

通知配信

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 随時（イベント駆動） |
| 実行時刻 | - |
| 実行曜日 | - |
| 実行日 | - |
| トリガー | Notifiable concern の after_create_commit コールバック |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Notifiableレコードが存在 | Event または Mention が作成されていること |
| 通知作成者がシステムユーザーでない | creator.system? が false であること |

### 実行可否判定

notifiableレコードがデシリアライズ可能な場合に実行。削除済みの場合はジョブを破棄。
Notifier#should_notify?でシステムユーザーからの通知は抑制。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| notifiable | Event/Mention | Yes | - | 通知対象のオブジェクト |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| events / mentions | DB | 通知ソースとなるイベント/メンション |
| users | DB | 通知対象ユーザー |
| cards | DB | 関連するカード情報 |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| notifications | DB | 通知レコードの作成・更新 |
| notification_bundles | DB | メールバンドル設定時 |

### 出力ファイル仕様

ファイル出力なし

## 処理フロー

### 処理シーケンス

```
1. notifiableオブジェクトを受け取り
   └─ ActiveJob::DeserializationErrorの場合はジョブを破棄
2. notifiable.notify_recipientsを呼び出し
   └─ Notifiable concernのメソッドを実行
3. Notifier.for(notifiable)で適切なNotifierを取得
   └─ EventならEventNotifier、MentionならMentionNotifier
4. Notifier#notifyで通知処理
   └─ should_notify?でシステムユーザーからの通知を除外
5. recipientsをIDでソートしてループ
   └─ デッドロック回避のため
6. 各recipientに対してNotification作成または更新
   └─ 既存の場合はunread_countをインクリメント
7. Notification作成後、bundleメソッドでバンドル処理
   └─ ユーザー設定によりメールバンドルに追加
```

### フローチャート

```mermaid
flowchart TD
    A[ジョブ開始] --> B{デシリアライズ成功?}
    B -->|失敗| C[ジョブ破棄]
    B -->|成功| D[notify_recipients呼び出し]
    D --> E[Notifier.for取得]
    E --> F{Notifier存在?}
    F -->|なし| G[処理終了]
    F -->|あり| H[notify呼び出し]
    H --> I{should_notify?}
    I -->|false| G
    I -->|true| J[recipientsをソート]
    J --> K[各recipientをループ]
    K --> L{既存Notification?}
    L -->|なし| M[Notification.create_or_find_by]
    L -->|あり| N[Notification更新]
    M --> O[bundle処理]
    N --> O
    O --> P{次のrecipient?}
    P -->|あり| K
    P -->|なし| Q[ジョブ終了]
```

## データベース操作仕様

### 操作別データベース影響一覧

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ソース取得 | events/mentions | SELECT | 通知ソース情報取得 |
| 通知作成 | notifications | INSERT | 新規通知作成 |
| 通知更新 | notifications | UPDATE | 既存通知更新 |
| バンドル追加 | notification_bundles | INSERT/UPDATE | メールバンドル |

### テーブル別操作詳細

#### notifications

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id | recipient.id | 通知対象ユーザー |
| INSERT | card_id | source.card_id | 関連カード |
| INSERT | source_type | Event/Mention | ポリモーフィック |
| INSERT | source_id | notifiable.id | ポリモーフィック |
| INSERT | creator_id | creator.id | 通知作成者 |
| INSERT | unread_count | 1 | 初期値 |
| UPDATE | source_type | Event/Mention | 更新時も必ず設定 |
| UPDATE | source_id | notifiable.id | 更新時 |
| UPDATE | read_at | nil | 既読解除 |
| UPDATE | unread_count | +1 | インクリメント |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ActiveJob::DeserializationError | notifiableが削除済み | ジョブを破棄（discard_on） |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | デフォルト |
| リトライ間隔 | デフォルト |
| リトライ対象エラー | DeserializationError以外 |

### 障害時対応

- DeserializationErrorの場合は自動的にジョブが破棄される
- その他のエラーはデフォルトのリトライ処理に従う
- デッドロック回避のためrecipientsをIDでソートしている

## トランザクション仕様

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | recipient単位 |
| コミットタイミング | 各Notification操作時 |
| ロールバック条件 | 個別操作失敗時 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 1イベントあたり数名〜数十名 |
| 目標処理時間 | 数秒以内 |
| メモリ使用量上限 | 特に制限なし |

## 排他制御

recipientsをIDでソートして処理することで、並行ジョブ間のデッドロックを回避。
また、create_or_find_byを使用して競合時の安全性を確保。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| 開始ログ | ジョブ開始時 | Active Jobデフォルト |
| 終了ログ | ジョブ終了時 | Active Jobデフォルト |
| エラーログ | エラー発生時 | Active Jobデフォルト |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| キュー滞留 | 設定依存 | 運用チーム |

## 備考

- Eventの場合は対応するEventNotifier（CardEventNotifier、CommentEventNotifier等）を使用
- Mentionの場合はMentionNotifierを使用
- 既存の通知がある場合は更新してunread_countをインクリメント
- source_type_will_change!を使用して、並行ジョブでのレース条件に対応
- Notification作成後、user.bundleでメールバンドル処理を行う
- PushNotificationJobはNotificationのsource_id変更時にトリガー
