# 機能設計書 144-Hookイベント処理

## 概要

本ドキュメントは、ChatwootにおけるHookイベント処理機能の設計書である。HookListenerがシステムイベントを監視し、登録された統合（Slack、Dialogflow、Google Translate、LeadSquared等）に対してイベントを配信する仕組みを提供する。

### 本機能の処理概要

**業務上の目的・背景**：Chatwootと外部サービス（Slack、Dialogflow、CRM等）との連携を実現するため、システム内で発生したイベント（メッセージ作成、連絡先更新、会話作成等）を検知し、適切な統合サービスに転送する必要がある。この機能により、各統合サービスがChatwootのイベントに対してリアルタイムに反応し、自動化された処理を実行することが可能になる。

**機能の利用シーン**：
- メッセージ作成時にSlackチャネルへ自動転送
- メッセージ受信時にDialogflowで自然言語処理を実行
- メッセージ作成時にGoogle Translateで言語検出
- 連絡先更新時にLeadSquared（CRM）へデータ同期
- 会話作成・解決時にCRMへアクティビティ記録

**主要な処理内容**：
1. HookListenerがWisperイベントを受信
2. イベントタイプに応じて対象のHookを特定
3. 統合サービスごとのサポートイベントをフィルタリング
4. HookJobを介した非同期でのイベント処理
5. 統合サービスごとの専用処理サービスを実行

**関連システム・外部連携**：
- Slack API（メッセージ送信）
- Google Dialogflow（自然言語処理）
- Google Cloud Translation API（言語検出）
- LeadSquared API（CRM連携）

**権限による制御**：イベント処理自体はシステムが自動実行するため、権限による制御は存在しない。ただし、Hookの登録・管理には管理者権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 65 | 連携アプリ一覧画面 | 参照画面 | 統合の有効化設定がイベント処理に影響 |
| 68 | Slack連携画面 | 参照画面 | Slack統合設定がmessage.createdイベント処理に影響 |

## 機能種別

イベント処理 / データ連携

## 入力仕様

### 入力パラメータ（イベントデータ）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| event | Object | Yes | Wisper/Railsイベントオブジェクト | - |
| event.name | String | Yes | イベント名（例: "message.created"） | - |
| event.data[:message] | Message | 条件付き | メッセージオブジェクト | メッセージ系イベント時 |
| event.data[:contact] | Contact | 条件付き | 連絡先オブジェクト | 連絡先系イベント時 |
| event.data[:conversation] | Conversation | 条件付き | 会話オブジェクト | 会話系イベント時 |

### 対応イベントと統合サービスのマッピング

| 統合サービス | 対応イベント |
|-------------|-------------|
| slack | message.created |
| dialogflow | message.created, message.updated |
| google_translate | message.created |
| leadsquared | contact.updated, conversation.created, conversation.resolved |

### 入力データソース

Wisperイベントバスからのイベント配信

## 出力仕様

### 出力データ

各統合サービスによって異なる：

| 統合サービス | 出力 |
|-------------|------|
| slack | Slackチャネルへのメッセージ投稿 |
| dialogflow | 会話への自動応答メッセージ |
| google_translate | メッセージへの言語情報付与 |
| leadsquared | CRMへのリード・アクティビティ作成 |

### 出力先

- 外部サービスAPI
- messagesテーブル（Dialogflow応答時）
- contactsテーブル（LeadSquared連携時）

## 処理フロー

### 処理シーケンス

```
1. イベント発生（Wisperイベントバス経由）
   └─ Rails各モデルで定義されたイベントがpublishされる

2. HookListener受信
   └─ 対応するメソッド（message_created, contact_updated等）が呼び出される

3. Hook検索
   └─ account.hooks.find_each
   └─ inbox単位のHookフィルタリング

4. サポートイベントチェック
   └─ supported_hook_event?(hook, event.name)
   └─ 統合サービスごとのイベントマッピング確認

5. HookJob実行
   └─ HookJob.perform_later(hook, event.name, event_data)

6. 統合サービス固有処理
   └─ Slack: SendOnSlackJob → SendOnSlackService
   └─ Dialogflow: ProcessorService
   └─ Google Translate: DetectLanguageService
   └─ LeadSquared: ProcessorService（Mutexロック付き）
```

### フローチャート

```mermaid
flowchart TD
    A[イベント発生] --> B[HookListener受信]
    B --> C[アカウントのHook一覧取得]
    C --> D{各Hookをループ}
    D --> E{inbox指定Hookか?}
    E -->|Yes| F{メッセージのinboxと一致?}
    F -->|No| D
    F -->|Yes| G{サポートイベント?}
    E -->|No| G
    G -->|No| D
    G -->|Yes| H{Hook有効?}
    H -->|No| D
    H -->|Yes| I[HookJob.perform_later]
    I --> D
    D -->|完了| J[終了]

    I --> K{app_id判定}
    K -->|slack| L[SendOnSlackJob]
    K -->|dialogflow| M[Dialogflow::ProcessorService]
    K -->|google_translate| N[GoogleTranslate::DetectLanguageService]
    K -->|leadsquared| O[Mutex取得 → Leadsquared::ProcessorService]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-144-01 | Hookステータス | disabled状態のHookはイベント処理をスキップ | 全イベント |
| BR-144-02 | インボックスフィルタ | inbox指定のHookはそのインボックスのイベントのみ処理 | Dialogflow等 |
| BR-144-03 | イベントマッピング | 各統合サービスはサポートするイベントのみ処理 | 全イベント |
| BR-144-04 | Slack添付ファイル遅延 | 添付ファイル付きメッセージは2秒遅延して送信 | Slack message.created |
| BR-144-05 | LeadSquared Mutex | LeadSquared処理はMutexロックで排他制御 | LeadSquared全イベント |
| BR-144-06 | フィーチャーフラグ | feature_allowed?がfalseの場合は処理スキップ | LeadSquared |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| イベント処理 | integrations_hooks | SELECT | アカウントのHook一覧取得 |
| Slack処理 | conversations | UPDATE | identifierの更新 |
| Slack処理 | messages | UPDATE | external_source_id_slack更新 |
| Dialogflow処理 | messages | INSERT | 自動応答メッセージ作成 |
| LeadSquared処理 | contacts | UPDATE | CRM連携IDの更新 |

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

#### integrations_hooks

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | account_id = message.account_id | Hook一覧取得 |
| SELECT | * | status = 'enabled' | 有効なHookのみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | StandardError | HookJob内でのエラー | ログ出力、処理継続 |
| - | LockAcquisitionError | Mutex取得失敗 | 3秒待機後リトライ（最大3回） |
| - | Slack API Error | Slack送信失敗 | Hookを無効化、再認証プロンプト |

### リトライ仕様

```ruby
# HookJob
retry_on LockAcquisitionError, wait: 3.seconds, attempts: 3

# SendOnSlackJob
retry_on LockAcquisitionError, wait: 1.second, attempts: 8
```

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

- イベント処理自体はトランザクション外で実行
- LeadSquared処理はRedis Mutexで排他制御
- 各統合サービス処理は独立したトランザクション

## パフォーマンス要件

- イベント受信からジョブキュー登録まで: 100ms以内
- Slack送信: 添付ファイルなし即時、あり2秒遅延

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

- Hookのaccess_tokenは暗号化されて保存
- 外部サービスへのAPI呼び出しは認証付き
- Slackエラー時は自動で無効化し再認証を促す

## 備考

- HookListenerはSingletonパターンで実装
- HookJobはMutexApplicationJobを継承しMutexロックをサポート
- 各統合サービスは独自のProcessorServiceを持つ

---

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

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

### 推奨読解順序

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

イベントとHookの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | hook.rb | `app/models/integrations/hook.rb` | Hookモデルの構造 |
| 1-2 | base_listener.rb | `app/listeners/base_listener.rb` | イベント抽出ヘルパー |

**読解のコツ**:
- **hook.rb**: app_id, status, inbox_idの関係を理解する
- **base_listener.rb**: extract_*メソッドでイベントからオブジェクトを取得

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

HookListenerの各イベントハンドラを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | hook_listener.rb | `app/listeners/hook_listener.rb` | イベントハンドラとフィルタリング |

**主要処理フロー**:
1. **2-6行目**: `message_created`でメッセージイベント処理
2. **14-22行目**: `contact_created/updated`でアカウント単位Hook処理
3. **24-35行目**: `conversation_created/resolved`で会話イベント処理
4. **39-48行目**: `execute_hooks`でメッセージ単位のHook実行
5. **50-56行目**: `execute_account_hooks`でアカウント単位のHook実行
6. **58-71行目**: `supported_hook_event?`でサポートイベントマッピング

#### Step 3: ジョブ処理を理解する

HookJobの処理フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | hook_job.rb | `app/jobs/hook_job.rb` | ジョブのディスパッチロジック |

**主要処理フロー**:
- **6-18行目**: `perform`メソッドでapp_idに応じた処理分岐
- **25-34行目**: `process_slack_integration`でSlack処理（遅延送信対応）
- **36-40行目**: `process_dialogflow_integration`でDialogflow処理
- **42-47行目**: `google_translate_integration`で言語検出処理
- **49-68行目**: `process_leadsquared_integration_with_lock`でMutex付きCRM処理

#### Step 4: 統合サービス処理を理解する

各統合サービスの処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | send_on_slack_service.rb | `lib/integrations/slack/send_on_slack_service.rb` | Slack送信処理 |
| 4-2 | send_on_slack_job.rb | `app/jobs/send_on_slack_job.rb` | Slack送信ジョブ |

**主要処理フロー**:
- **send_on_slack_job.rb 5-9行目**: Mutexロック取得後にサービス実行
- **send_on_slack_service.rb 5-14行目**: バリデーションとメッセージ送信

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

```
[イベント発生]
    │
    ▼
HookListener (singleton)
    │
    ├─ #message_created / #message_updated
    │      └─ execute_hooks
    │             ├─ hook.inbox != message.inbox → skip
    │             └─ supported_hook_event? → HookJob.perform_later
    │
    ├─ #contact_created / #contact_updated
    │      └─ execute_account_hooks
    │             └─ account_hooks.find_each → HookJob.perform_later
    │
    └─ #conversation_created / #conversation_resolved
           └─ execute_account_hooks
                  └─ account_hooks.find_each → HookJob.perform_later

HookJob
    │
    ├─ app_id: 'slack'
    │      └─ SendOnSlackJob.perform_later
    │             └─ SendOnSlackService.new.perform
    │
    ├─ app_id: 'dialogflow'
    │      └─ Dialogflow::ProcessorService.new.perform
    │
    ├─ app_id: 'google_translate'
    │      └─ GoogleTranslate::DetectLanguageService.new.perform
    │
    └─ app_id: 'leadsquared'
           └─ with_lock(mutex)
                  └─ Leadsquared::ProcessorService.new
                         ├─ handle_contact
                         ├─ handle_conversation_created
                         └─ handle_conversation_resolved
```

### データフロー図

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

Wisperイベント         ───▶ HookListener              ───▶ HookJob
(message, contact,              │                           │
 conversation)                  ▼                           ▼
                         Hook検索                    統合サービス処理
                         フィルタリング                    │
                                                         ├─▶ Slack API
                                                         ├─▶ Dialogflow API
                                                         ├─▶ Google Translate API
                                                         └─▶ LeadSquared API
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| hook_listener.rb | `app/listeners/hook_listener.rb` | リスナー | イベント受信とHook実行 |
| hook_job.rb | `app/jobs/hook_job.rb` | ジョブ | 非同期処理のディスパッチ |
| send_on_slack_job.rb | `app/jobs/send_on_slack_job.rb` | ジョブ | Slack送信ジョブ |
| send_on_slack_service.rb | `lib/integrations/slack/send_on_slack_service.rb` | サービス | Slack送信処理 |
| processor_service.rb | `lib/integrations/dialogflow/processor_service.rb` | サービス | Dialogflow処理 |
| detect_language_service.rb | `lib/integrations/google_translate/detect_language_service.rb` | サービス | 言語検出処理 |
| processor_service.rb | `lib/crm/leadsquared/processor_service.rb` | サービス | LeadSquared処理 |
| mutex_application_job.rb | `app/jobs/mutex_application_job.rb` | 基底クラス | Mutexロック機能 |
