# SOrchestrator で依存解決と実 DML 挿入

`SBlueprint` で組み立てた設計図を **実レコードに変換するエンジン** が `SOrchestrator` です。親 → 子 の挿入順、親 Id を子へ転記する処理、alias で参照した値の解決、これらをすべて自動でやってくれます。

このページでは、`SOrchestrator` の 4 つのメソッド (`start` / `add` / `create` / `getByAlias`) を順に解説し、最後に実運用でハマりやすいポイントをまとめます。

## SOrchestrator.start(): builder の初期化

すべての操作はこの静的メソッドから始まります。戻り値は空の `SOrchestrator` インスタンスで、そこに blueprint を `.add(...)` して、最後に `.create()` で実行する、という流れになります。

**シグネチャ:** `SOrchestrator.start()`

```apex
SOrchestrator orchestrator = SOrchestrator.start();
```

## .add(blueprint): blueprint をキューに登録する

`.add(...)` は 1 つの `SBlueprint` を SOrchestrator のキューに登録します。

**シグネチャ:** `.add(SBlueprint blueprint)`

```apex
SOrchestrator.start()
    .add(
        SBlueprint.of(Account.class)
            .template(Blueprints.accBasic())
            .alias('parentAccount')
    )
    .add(
        SBlueprint.of(Opportunity.class)
            .set('Name', 'Test Opportunity')
            .set('StageName', 'Prospecting')
            .set('CloseDate', Date.today().addDays(30))
            .use('parentAccount', 'Id', 'AccountId')
            .alias('targetOpp')
    );
```

### 振る舞いの要点

- **追加順は無視される**。SOrchestrator が内部で依存関係を解析し、トポロジカルソートで insert 順を再決定する
- 子を先に書いて親を後に書いても問題ない。「読みやすい順」で書ける
- 1 つの SOrchestrator に複数の `.add(...)` を連続して呼べる
- 親子関係を 1 つの blueprint 内で表現する場合 (`withChildren`) は、親側だけを `.add(...)` すれば子も一緒に登録される (詳細は [親子・量産・参照のパターン](/ja/apex-stem/docs/apex-blueprint-relations-and-bulk))

## .create(): 依存解決して DML 挿入する

`.create()` は、登録された blueprint 群を解析し、**正しい順序で DML insert を実行** します。この呼び出しが終わった時点で、全レコードがデータベースに反映され、Id も払い出されています。

**シグネチャ:** `.create()`

```apex
SOrchestrator orchestrator = SOrchestrator.start()
    .add(/* ... */)
    .add(/* ... */);
orchestrator.create();
```

### 振る舞いの要点

- 内部で **トポロジカルソート** を実行し、依存される側 (親) から先に insert する
- `.use('alias', 'Id', 'AccountId')` のような Id 参照は、親が insert された直後の Id を子のフィールドにコピーしてから子を insert する
- DML 失敗時は通常の Apex DML 例外がそのまま投げられる
- **戻り値は `void`**。`.create()` を `.add(...)` チェーンの末尾に繋いで変数へ代入することはできない。一旦 `SOrchestrator` を変数に受けてから `orchestrator.create()` を呼び、その同じ変数で `getByAlias(...)` する

## .getByAlias(name): 生成後のレコードを取り出す

`create()` 後、alias で生成済みレコードを取り出せます。SOQL を書かずに「先ほど作った Account」を手元に持ってこられるので、アサーションの記述が短くなります。

**シグネチャ:** `.getByAlias(String aliasName)` (戻り値は `SObject`)

```apex
SOrchestrator orchestrator = SOrchestrator.start()
    .add(
        SBlueprint.of(Account.class)
            .template(Blueprints.accBasic())
            .alias('parentAccount')
    );
orchestrator.create();

Account parent = (Account) orchestrator.getByAlias('parentAccount');
Assert.isNotNull(parent.Id);
```

### 振る舞いの要点

- 戻り値は `SObject` 型なので、利用側で目的の SObject 型にキャストする
- **存在しない alias を渡すと `null` が返る** (例外ではない)。typo に気付きにくいので、取り出した直後に `Assert.isNotNull(...)` で守るのが安全
- `.times(n)` と `'{#}'` プレースホルダ付きの alias (例: `'con_{#}'`) で量産したレコードは、`'con_1'` / `'con_2'` / ... のように展開後の alias で個別に取り出せる (詳細は [親子・量産・参照のパターン](/ja/apex-stem/docs/apex-blueprint-relations-and-bulk))

## 例: 完全なテストワークフロー

`start` → `add` → `create` → `getByAlias` までを 1 つのテストに通すと、こうなります。

```apex
@isTest
static void testOppCreationWithAccount() {
    SOrchestrator orchestrator = SOrchestrator.start()
        .add(
            SBlueprint.of(Account.class)
                .template(Blueprints.accBasic())
                .alias('parentAccount')
        )
        .add(
            SBlueprint.of(Opportunity.class)
                .set('Name', 'Test Opportunity')
                .set('StageName', 'Prospecting')
                .set('CloseDate', Date.today().addDays(30))
                .use('parentAccount', 'Id', 'AccountId')
                .alias('targetOpp')
        );
    orchestrator.create();

    Account parent = (Account) orchestrator.getByAlias('parentAccount');
    Opportunity opp = (Opportunity) orchestrator.getByAlias('targetOpp');

    Assert.areEqual(parent.Id, opp.AccountId);
    Assert.areEqual('Test Opportunity', opp.Name);
}
```

ポイント:

- **テスト本体に SOQL が一行も無い**。アサーション対象は `getByAlias(...)` で直接取り出せている
- `Account` を先に `.add(...)` しているが、仮にここで `Opportunity` を先に `.add(...)` しても、SOrchestrator が依存解析で並び替えるので結果は同じ
- 親 Id は `.use('parentAccount', 'Id', 'AccountId')` の宣言だけで子に転記される。「親を insert して Id を変数に取り、子の AccountId に代入して...」という手続きは消える

## ハマりどころ

### 循環依存

`A.use('B', ...)` と `B.use('A', ...)` の両方が成り立つような依存を作ると、トポロジカルソートが解けず `Circular or invalid reference detected` で `.create()` 時に失敗します。設計を見直して、一方を片方向の参照にするか、親子関係を `withChildren` に置き換えるかで解消します。

### alias の重複

同じ `SOrchestrator` 内で `.alias('foo')` が 2 か所にあると `Duplicate alias detected` で失敗します。`.times(n)` で量産する場合は `'foo_{#}'` のようにプレースホルダ付きで宣言することで、展開後に一意な alias が払い出されます。

### 存在しない alias の参照

`.use('typoAlias', ...)` のように、どこにも宣言されていない alias を参照すると、同じく `Circular or invalid reference detected` 系のエラーで `.create()` が失敗します。alias の typo は気付きにくいので、「`.use` の第 1 引数」と「対応する `.alias`」をペアで見直す習慣をつけると安全です。

### 自動 alias は取り出し用には使わない

`.alias(...)` を省略しても blueprint は問題なく登録できますが、内部的に `__Account_0_1__` のような **自動 alias** が割り振られます。これを後から `getByAlias` で取り出すのは現実的ではありません。**`getByAlias` で取り出す予定がある blueprint には必ず明示的に `.alias(...)` を付ける** のが原則です。

## 関連ドキュメント

- [SBlueprint で単一レコードを宣言する](/ja/apex-stem/docs/apex-blueprint-sblueprint-guide): `.add(...)` に渡す設計図の作り方
- [親子・量産・参照のパターン](/ja/apex-stem/docs/apex-blueprint-relations-and-bulk): `withChildren` / `times` / `{#}` / `{P0}` `{P1}` などの応用
- [API リファレンス: SOrchestrator](/ja/apex-stem/docs/apex-blueprint-api-sorchestrator): 全メソッドのシグネチャ網羅
- [ApexBlueprint ガイドへ戻る](/ja/apex-stem/docs/apex-blueprint-guide)
