SOrchestrator で依存解決と実 DML 挿入
SBlueprint で組み立てた設計図を 実レコードに変換するエンジン が SOrchestrator です。親 → 子 の挿入順、親 Id を子へ転記する処理、alias で参照した値の解決、これらをすべて自動でやってくれます。
このページでは、SOrchestrator の 4 つのメソッド (start / add / create / getByAlias) を順に解説し、最後に実運用でハマりやすいポイントをまとめます。
SOrchestrator.start(): builder の初期化
すべての操作はこの静的メソッドから始まります。戻り値は空の SOrchestrator インスタンスで、そこに blueprint を .add(...) して、最後に .create() で実行する、という流れになります。
シグネチャ: SOrchestrator.start()
SOrchestrator orchestrator = SOrchestrator.start();
.add(blueprint): blueprint をキューに登録する
.add(...) は 1 つの SBlueprint を SOrchestrator のキューに登録します。
シグネチャ: .add(SBlueprint blueprint)
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(...)すれば子も一緒に登録される (詳細は 親子・量産・参照のパターン)
.create(): 依存解決して DML 挿入する
.create() は、登録された blueprint 群を解析し、正しい順序で DML insert を実行 します。この呼び出しが終わった時点で、全レコードがデータベースに反映され、Id も払い出されています。
シグネチャ: .create()
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)
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 で個別に取り出せる (詳細は 親子・量産・参照のパターン)
例: 完全なテストワークフロー
start → add → create → getByAlias までを 1 つのテストに通すと、こうなります。
@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 で単一レコードを宣言する:
.add(...)に渡す設計図の作り方 - 親子・量産・参照のパターン:
withChildren/times/{#}/{P0}{P1}などの応用 - API リファレンス: SOrchestrator: 全メソッドのシグネチャ網羅
- ApexBlueprint ガイドへ戻る