# 通知設計書 3-ChatNotification

## 概要

本ドキュメントは、Symfony NotifierコンポーネントにおけるChat通知（ChatNotification）の設計仕様を記述する。ChatNotificationInterfaceを実装した通知オブジェクトがChatChannelを通じてチャットメッセージ（Slack、Discord、Telegram等）を送信する仕組みについて詳細に定義する。

### 本通知の処理概要

ChatNotificationは、Symfony Notifierコンポーネントが提供するチャット送信チャネルを通じた通知機能である。ChatNotificationInterfaceを実装することでSlack、Discord、Telegram、Microsoft Teams等の各種チャットサービスに対してカスタムメッセージを生成・送信できる。

**業務上の目的・背景**：チャットツールは現代の開発・運用チームにおいて主要なコミュニケーション手段であり、システムアラート、デプロイ通知、CI/CD結果の報告など、チーム全体への即時共有が必要なシナリオで使用される。Notifierコンポーネントにより、複数のチャットプラットフォームを統一的なインターフェースで扱える。

**通知の送信タイミング**：Notifier::send()が呼び出され、Notificationのchannelsに"chat"が含まれている場合、またはChannelPolicyでchatチャネルが選択された場合に送信が実行される。"chat/slack"のようにチャネル名にTransport名を含めることで特定のチャットサービスを指定できる。

**通知の受信者**：ChatChannelはすべてのRecipientInterfaceをサポートする（supports()が常にtrueを返す）。チャットの宛先（チャネル、ルーム等）はMessageOptionsInterfaceを通じて設定される。

**通知内容の概要**：メッセージ本文はNotification::getSubject()から取得され、ChatMessageオブジェクトとして送信される。各チャットサービス固有のオプション（メンション、ボタン、フォーマット等）はMessageOptionsInterfaceで指定可能。

**期待されるアクション**：受信者はチャットメッセージを確認し、内容に応じた対応（アラートへの対応、リンクのクリック、ディスカッションへの参加など）を行うことが期待される。

## 通知種別

チャット（Slack / Discord / Telegram / Microsoft Teams 等）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（Transport直接送信）または非同期（Messenger Bus経由） |
| 優先度 | Notificationのimportanceに依存（メッセージ本体には直接反映されない） |
| リトライ | Notifierコンポーネント自体にはリトライ機構なし。Messenger利用時はMessengerのリトライ機構に依存 |

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

1. ChatChannel::supports()は常にtrueを返すため、すべてのRecipientに対して利用可能（ChatChannel.php 行45-47）
2. 通知オブジェクトがChatNotificationInterfaceを実装している場合、asChatMessage()メソッドでカスタムChatMessageを生成（ChatChannel.php 行27-29）
3. カスタムメッセージがnullの場合、ChatMessage::fromNotification()によりデフォルトのChatMessageを生成（ChatChannel.php 行31）
4. 具体的な送信先（チャネル名、ルームID等）はChatMessage::getOptions()内のMessageOptionsInterfaceで指定

## 通知テンプレート

### 本文テンプレート

```
{Notification::getSubject()の内容}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| 該当なし | - | - | 基本的なChatMessageには添付ファイル機能なし。各サービス固有のOptionsで対応 |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| subject | メッセージ本文 | Notification::getSubject() | Yes |
| options | チャットサービス固有オプション | ChatMessage::getOptions() | No |
| recipientId | 宛先識別子 | MessageOptionsInterface::getRecipientId() | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | Notifier::send()の実行 | Notificationのchannelsに"chat"が含まれる、またはChannelPolicyでchatが選択される | "chat/slack"のようにTransport名指定も可能 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| なし | ChatChannel::supports()は常にtrueを返すため、Recipient側の制限はない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Notifier::send呼び出し] --> B[getChannels: チャネル決定]
    B --> C[ChatChannel::supports チェック]
    C -->|常にtrue| D[ChatChannel::notify 実行]
    D --> E{ChatNotificationInterface?}
    E -->|Yes| F[asChatMessage でカスタムメッセージ生成]
    E -->|No| G[ChatMessage::fromNotification でデフォルト生成]
    F --> H[Transport名設定]
    G --> H
    H --> I{Bus設定あり?}
    I -->|Yes| J[Messenger Bus経由で非同期送信]
    I -->|No| K[Transport直接送信]
    J --> L[終了]
    K --> L
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| 該当なし | - | Notifierコンポーネント自体はデータベースを直接参照しない |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| 該当なし | - | Notifierコンポーネント自体はデータベースを直接更新しない |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| LogicException | TransportもBusも両方nullの場合（AbstractChannel.php 行28-30） | ChatChannelのコンストラクタでTransportまたはBusの少なくとも一方を指定する |
| TransportExceptionInterface | チャットサービスへの送信時のAPIエラー | アプリケーション側でcatchして適切に処理する |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Notifierコンポーネント自体にはリトライ機構なし |
| リトライ間隔 | Messenger利用時はMessengerのリトライ設定に依存 |
| リトライ対象エラー | Messenger利用時はMessengerのリトライポリシーに依存 |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | チャットサービスプロバイダのAPI制限に依存 |
| 1日あたり上限 | 同上 |

### 配信時間帯

Notifierコンポーネント自体に配信時間帯の制限機能はない。

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

- チャットサービスのAPIトークン/Webhook URLは機密情報として環境変数で管理すること
- 通知内容に機密情報が含まれる場合、チャットチャネルのアクセス権限を適切に設定すること
- Webhook URLの漏洩防止のため、ログ出力時にはURLをマスキングすること

## 備考

- ChatChannelはAbstractChannelを継承しており、Transport/Busの初期化ロジックは共通化されている
- ChatMessage::fromNotification()ではNotification::getSubject()のみがメッセージ本文として使用される（recipientは使用されない）
- 各チャットサービス固有の機能（Slackのブロックキット、Telegramのマークダウン等）はMessageOptionsInterfaceの実装で対応する
- "chat/slack"のようにチャネル名にスラッシュでTransport名を付加することで、特定のチャットサービスを指定可能（Notifier.php 行85-88）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ChatNotificationInterface.php | `src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php` | asChatMessage()のシグネチャ（行22）。RecipientInterfaceを受け取る（EmailやSMSと異なり特定のRecipient型を要求しない） |
| 1-2 | ChatMessage.php | `src/Symfony/Component/Notifier/Message/ChatMessage.php` | コンストラクタ（行24-28）でsubjectとoptionsを受け取る。fromNotification()（行30-36）ではsubjectのみ使用 |
| 1-3 | MessageOptionsInterface.php | `src/Symfony/Component/Notifier/Message/MessageOptionsInterface.php` | getRecipientId()を定義。チャットサービス固有のオプションはこのインターフェースの実装で提供される |

**読解のコツ**: ChatMessage::fromNotification()（行30-36）はrecipientパラメータを受け取らない点に注意。EmailやSMSと異なり、宛先はメッセージオプションで指定される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Notifier.php | `src/Symfony/Component/Notifier/Notifier.php` | getChannels()（行83-88）でチャネル名にスラッシュが含まれる場合のTransport名分離ロジックを理解 |

#### Step 3: チャネル処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ChatChannel.php | `src/Symfony/Component/Notifier/Channel/ChatChannel.php` | notify()（行24-42）の処理フロー。supports()（行45-47）が常にtrueを返す点に注目 |

**主要処理フロー**:
- **行27-29**: ChatNotificationInterface実装時はasChatMessage()でカスタムメッセージ生成
- **行31**: デフォルトはChatMessage::fromNotification(notification)（recipientなし）
- **行33-35**: transportName設定
- **行37-41**: Bus有無による送信方式の分岐

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

```
Notifier::send()
    |
    +-- getChannels()
    |       +-- チャネル名からTransport名を分離（例: "chat/slack" -> channel="chat", transport="slack"）
    |
    +-- ChatChannel::supports()  [常にtrue]
    |
    +-- ChatChannel::notify()
            |
            +-- [ChatNotificationInterface] notification->asChatMessage()
            |       または
            +-- ChatMessage::fromNotification(notification)
            |
            +-- ChatMessage::transport() [transportName設定]
            |
            +-- [Bus == null] Transport::send(message)
            +-- [Bus != null] Bus::dispatch(message)
```

### データフロー図

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

Notification                    Notifier::send()
  - subject          -------->    |
  - channels                      v
                               ChatChannel::notify()
RecipientInterface                |
  （任意の実装）     -------->    v                    -------->  チャット送信
                               ChatMessage生成                    (Transport or Bus)
MessageOptions                    |
  - recipientId      -------->    v
  - サービス固有設定           送信実行
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ChatNotificationInterface.php | `src/Symfony/Component/Notifier/Notification/ChatNotificationInterface.php` | ソース | Chat通知インターフェース |
| ChatChannel.php | `src/Symfony/Component/Notifier/Channel/ChatChannel.php` | ソース | チャット送信チャネル |
| AbstractChannel.php | `src/Symfony/Component/Notifier/Channel/AbstractChannel.php` | ソース | チャネル基底クラス |
| ChatMessage.php | `src/Symfony/Component/Notifier/Message/ChatMessage.php` | ソース | チャットメッセージクラス |
| Notification.php | `src/Symfony/Component/Notifier/Notification/Notification.php` | ソース | 通知基本クラス |
| Notifier.php | `src/Symfony/Component/Notifier/Notifier.php` | ソース | 通知送信エントリーポイント |
| RecipientInterface.php | `src/Symfony/Component/Notifier/Recipient/RecipientInterface.php` | ソース | 受信者基底インターフェース |
