# 機能設計書 40-domain

## 概要

本ドキュメントは、Node.js の `domain` モジュール（エラードメイン管理）の機能設計書である。このモジュールは、複数の非同期操作を単一のグループとして扱い、エラーを一元的に処理するための機能を提供する。

**注意：このモジュールは非推奨（pending deprecation）である。新しいコードでは `async_hooks` や `AsyncLocalStorage` の使用が推奨される。**

### 本機能の処理概要

domain モジュールは、非同期操作のエラーを一元的にキャッチするための仕組みを提供する。ドメイン内で発生した未処理の例外やエラーイベントを、ドメインの `error` イベントで受け取ることができる。

**業務上の目的・背景**：Node.jsの非同期処理では、try-catchで例外をキャッチできない場合がある。domainモジュールは、複数の非同期操作を1つのグループとして管理し、そのグループ内で発生したエラーを統一的に処理する手段を提供する。

**機能の利用シーン**：
- 非同期操作のエラーを一元管理したい場合
- リクエストごとにエラー処理を分離したい場合（HTTPサーバー等）
- 複数のEventEmitterのエラーを1箇所で処理したい場合
- コンテキスト情報をエラーに付与したい場合

**主要な処理内容**：
1. ドメインの作成（`create()`）
2. ドメインへの進入・退出（`enter()` / `exit()`）
3. EventEmitter/タイマーの追加（`add()` / `remove()`）
4. ドメイン内での関数実行（`run()`）
5. コールバックのバインド（`bind()` / `intercept()`）
6. エラーハンドリング（`error` イベント）

**関連システム・外部連携**：
- `async_hooks` モジュール（非同期コンテキスト追跡）
- `events` モジュール（EventEmitter 基盤）
- `process` オブジェクト（uncaughtException 処理）

**権限による制御**：特段の権限制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | ターミナルベースのCLIのため画面なし |

## 機能種別

エラー処理 / 非同期コンテキスト管理 / 例外捕捉

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| fn | Function | Yes | run()で実行する関数 | 関数であること |
| cb | Function | Yes | bind()/intercept()でバインドするコールバック | 関数であること |
| ee | EventEmitter | Yes | add()/remove()で管理するEventEmitter | EventEmitterであること |

### 入力データソース

- アプリケーションコードからの呼び出し
- 非同期操作からのコールバック

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| domain | Domain | 作成されたドメインインスタンス |
| active | Domain/null | 現在アクティブなドメイン |
| members | Array | ドメインに追加されたメンバー |

### 出力先

- `domain.active`（現在のアクティブドメイン）
- `process.domain`（プロセスレベルのドメイン参照）
- `error` イベントのコールバック

## 処理フロー

### 処理シーケンス

```
1. ドメイン作成
   └─ domain.create() でDomainインスタンス作成
2. ドメイン進入
   └─ domain.enter() でスタックにプッシュ
3. 非同期操作実行
   └─ domain.run(fn) でドメイン内で関数実行
4. エラー発生時
   └─ _errorHandler() でエラー処理
5. ドメイン退出
   └─ domain.exit() でスタックからポップ
```

### フローチャート

```mermaid
flowchart TD
    A[domain.create/] --> B[new Domain/]
    B --> C[asyncHook.enable/]

    D[domain.run/fn] --> E[domain.enter/]
    E --> F[fn.call/domain, args]
    F --> G{エラー発生?}
    G -->|Yes| H[_errorHandler/er]
    G -->|No| I[domain.exit/]

    H --> J{errorリスナーあり?}
    J -->|Yes| K[emit/'error', er]
    J -->|No| L[uncaughtException]
    K --> M[caught = true]
    M --> N[domainUncaughtExceptionClear/]

    O[domain.add/ee] --> P[ee.domain = this]
    P --> Q[members.push/ee]

    R[domain.bind/cb] --> S[runBound作成]
    S --> T[runBound.domain = this]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | スタック管理 | ドメインはスタック構造で管理される | 常時 |
| BR-02 | エラー伝搬 | エラーは現在のドメインの error イベントで処理 | ドメインアクティブ時 |
| BR-03 | 循環参照防止 | ドメイン間の循環参照は add() で防止される | Domain を add() する時 |
| BR-04 | 非推奨 | このモジュールは非推奨であり、新規使用は推奨されない | 常時 |
| BR-05 | 排他制御 | domain使用中は setUncaughtExceptionCaptureCallback 使用不可 | domain require 時 |

### 計算ロジック

特段の複雑な計算ロジックはなし。

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

該当なし

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

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ERR_DOMAIN_CALLBACK_NOT_AVAILABLE | Error | 既存のuncaughtException捕捉がある状態でdomainをrequire | 先にdomain.requireするか、AsyncLocalStorageを使用 |
| ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE | Error | domain使用中にsetUncaughtExceptionCaptureCallback呼び出し | domainとの併用は不可 |
| ERR_UNHANDLED_ERROR | Error | errorイベントのエラー引数なし | エラーオブジェクトを渡す |

### リトライ仕様

リトライは行わない。

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

該当なし

## パフォーマンス要件

- async_hooksを使用するため、パフォーマンスオーバーヘッドが発生する
- ドメインスタックの操作は軽量

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

- VM モジュールからのPromiseには domain プロパティを設定しない（サンドボックスエスケープ防止）
- エラーオブジェクトに `domainThrown` プロパティが設定される

## 備考

- このモジュールは非推奨（pending deprecation）である
- 代替として `async_hooks` の `AsyncLocalStorage` が推奨される
- `EventEmitter.usingDomains = true` が設定され、EventEmitterの動作が変更される
- DEP0097: MakeCallback での domain プロパティ使用は非推奨

---

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

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

### 推奨読解順序

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

まず、domain モジュールの基本構造とエクスポートを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | domain.js | `lib/domain.js` | module.exports と Domain クラス（214-231行目） |

**読解のコツ**: Domain クラスは `EventEmitter` を継承している。`members` 配列でEventEmitterを管理し、`kWeak` シンボルで WeakReference を保持する。

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

モジュールのエクスポートと主要関数を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | domain.js | `lib/domain.js` | createHook インポート（53行目） |
| 2-2 | domain.js | `lib/domain.js` | process.domain 定義（62-71行目） |
| 2-3 | domain.js | `lib/domain.js` | asyncHook 定義（75-120行目） |
| 2-4 | domain.js | `lib/domain.js` | exports.create（229-231行目） |

**主要処理フロー**:
1. **53行目**: async_hooks から createHook をインポート
2. **62-71行目**: process.domain を getter/setter で定義
3. **75-120行目**: asyncHook で非同期操作をドメインに関連付け
4. **229-231行目**: create() で新しい Domain インスタンスを作成

#### Step 3: Domain クラスの実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | domain.js | `lib/domain.js` | Domain コンストラクタ（214-225行目） |
| 3-2 | domain.js | `lib/domain.js` | enter/exit メソッド（320-340行目） |
| 3-3 | domain.js | `lib/domain.js` | add/remove メソッド（344-384行目） |
| 3-4 | domain.js | `lib/domain.js` | run メソッド（387-393行目） |

**主要処理フロー**:
- **214-225行目**: コンストラクタ - members 初期化、WeakReference 作成、asyncHook 有効化
- **320-326行目**: enter() - スタックにプッシュ、process.domain 更新
- **329-340行目**: exit() - スタックからポップ、process.domain 更新
- **344-376行目**: add() - EventEmitter をドメインに追加
- **379-384行目**: remove() - EventEmitter をドメインから削除
- **387-393行目**: run() - ドメイン内で関数を実行

#### Step 4: エラーハンドリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | domain.js | `lib/domain.js` | _errorHandler メソッド（238-317行目） |
| 4-2 | domain.js | `lib/domain.js` | updateExceptionCapture（174-184行目） |
| 4-3 | domain.js | `lib/domain.js` | domainUncaughtExceptionClear（207-211行目） |

**主要処理フロー**:
- **238-317行目**: _errorHandler - エラーを処理し、error イベントを発火
- **241-250行目**: エラーオブジェクトに domain と domainThrown プロパティを設定
- **255-257行目**: 現在のドメインから exit
- **268-283行目**: トップレベルドメインのエラー処理
- **284-308行目**: ネストされたドメインのエラー処理

#### Step 5: バインドとインターセプトを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | domain.js | `lib/domain.js` | bind メソッド（440-456行目） |
| 5-2 | domain.js | `lib/domain.js` | intercept メソッド（420-428行目） |
| 5-3 | domain.js | `lib/domain.js` | intercepted 関数（396-417行目） |

**主要処理フロー**:
- **440-456行目**: bind() - コールバックをドメインにバインド
- **420-428行目**: intercept() - エラーファーストコールバックをインターセプト
- **396-417行目**: intercepted - 最初の引数がエラーなら error イベントを発火

#### Step 6: EventEmitter の拡張を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | domain.js | `lib/domain.js` | EventEmitter.usingDomains（459行目） |
| 6-2 | domain.js | `lib/domain.js` | EventEmitter.init 拡張（462-475行目） |
| 6-3 | domain.js | `lib/domain.js` | EventEmitter.emit 拡張（478-556行目） |

**主要処理フロー**:
- **459行目**: EventEmitter.usingDomains = true でドメイン対応を有効化
- **462-475行目**: init - 新しい EventEmitter に domain プロパティを設定
- **478-556行目**: emit - ドメインコンテキストでイベントを発火

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

```
domain.create()
    │
    └─ new Domain()
           │
           ├─ EventEmitter.call(this)
           │
           ├─ this.members = []
           │
           ├─ this[kWeak] = new WeakReference(this)
           │
           └─ asyncHook.enable()

domain.run(fn, ...args)
    │
    ├─ this.enter()
    │      │
    │      ├─ exports.active = process.domain = this
    │      │
    │      ├─ stack.push(this)
    │      │
    │      └─ updateExceptionCapture()
    │
    ├─ ReflectApply(fn, this, args)
    │
    └─ this.exit()
           │
           ├─ stack.splice(index)
           │
           ├─ exports.active = stack[stack.length - 1]
           │
           └─ updateExceptionCapture()

domain.add(ee)
    │
    ├─ ee.domain 確認（既存なら remove）
    │
    ├─ 循環参照チェック
    │
    ├─ ee.domain = this
    │
    └─ this.members.push(ee)

domain._errorHandler(er)
    │
    ├─ er.domain = this
    │
    ├─ er.domainThrown = true
    │
    ├─ while (exports.active === this) this.exit()
    │
    ├─ this.emit('error', er)
    │      │
    │      └─ caught = true/false
    │
    └─ domainUncaughtExceptionClear()

asyncHook (init/before/after/destroy)
    │
    ├─ init: pairing.set(asyncId, process.domain[kWeak])
    │
    ├─ before: current.get().enter()
    │
    ├─ after: domain.exit()
    │
    └─ destroy: pairing.delete(asyncId)
```

### データフロー図

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

domain.create() ────────▶ new Domain() ─────────────────▶ Domain インスタンス

domain.run(fn) ─────────▶ enter() → fn() → exit() ─────▶ fn の戻り値
                               │
                               └─ エラー → _errorHandler() → 'error' イベント

domain.add(ee) ─────────▶ ee.domain = this ─────────────▶ members に追加
                               │
                               └─ ee.emit('error') → domain.emit('error')

非同期操作 ─────────────▶ asyncHook ─────────────────────▶ ドメインコンテキスト伝搬
      │                        │
      ▼                        ▼
  init ────────────────▶ pairing.set() ───────────────▶ asyncId → domain マッピング
      │
      ▼
  before ──────────────▶ domain.enter() ──────────────▶ アクティブドメイン設定
      │
      ▼
  after ───────────────▶ domain.exit() ───────────────▶ ドメイン退出
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| domain.js | `lib/domain.js` | ソース | メインモジュール実装 |
| async_hooks.js | `lib/async_hooks.js` | ソース | 非同期コンテキスト追跡 |
| events.js | `lib/events.js` | ソース | EventEmitter 基盤 |
| internal/async_hooks.js | `lib/internal/async_hooks.js` | ソース | 内部async_hooks実装 |
| internal/errors.js | `lib/internal/errors.js` | ソース | エラーコード定義 |
| internal/util.js | `lib/internal/util.js` | ソース | WeakReference 提供 |
