ApexBlueprint: Dependency resolution and DML execution with SOrchestrator
SOrchestrator is the engine that turns blueprints assembled by SBlueprint into real records. It automatically handles the parent → child insertion order, copying parent Ids into children, and resolving values referenced via aliases.
This page walks through the four methods of SOrchestrator (start / add / create / getByAlias) in order, and ends with a list of common pitfalls in real-world usage.
SOrchestrator.start(): Initialize the Builder
Every operation begins with this static method. It returns an empty SOrchestrator instance; you .add(...) blueprints to it, then call .create() to execute.
Signature: SOrchestrator.start()
SOrchestrator orchestrator = SOrchestrator.start();
.add(blueprint): Register a Blueprint in the Queue
.add(...) registers a single SBlueprint into SOrchestrator's queue.
Signature: .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')
);
Behavior Notes
- Addition order is ignored. SOrchestrator analyzes the dependencies internally and re-derives the insertion order via topological sort
- It is fine to write the child before the parent. You can write blueprints in "the most readable order"
- Multiple
.add(...)calls can be chained on the same SOrchestrator - If you express parent-child relationships within a single blueprint via
withChildren, registering the parent alone is enough — children come along with it (see Relations, Bulk Generation, and Reference Patterns)
.create(): Resolve Dependencies and Run DML
.create() analyzes the registered blueprints and executes the DML inserts in the correct order. By the time this call returns, every record is in the database and Ids have been issued.
Signature: .create()
SOrchestrator orchestrator = SOrchestrator.start()
.add(/* ... */)
.add(/* ... */);
orchestrator.create();
Behavior Notes
- Internally runs a topological sort and inserts the depended-upon side (parents) first
- Id references like
.use('alias', 'Id', 'AccountId')copy the parent's Id (issued right after its insert) into the child's field before inserting the child - DML failures throw the usual Apex DML exceptions
- The return value is
void. You cannot append.create()to the.add(...)chain and assign it to a variable. Capture theSOrchestratorin a variable first, callorchestrator.create(), then callgetByAlias(...)on that same variable
.getByAlias(name): Retrieve a Created Record
After create(), you can retrieve the created records by alias. You can bring "the Account you just created" to your hand without writing a SOQL query, which keeps assertion code short.
Signature: .getByAlias(String aliasName) (returns 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);
Behavior Notes
- The return value is
SObject, so the caller casts it to the desired SObject type - Passing a non-existent alias returns
null(not an exception). Typos are easy to miss, so guard withAssert.isNotNull(...)right after retrieval as a safe default - Records bulked via
.times(n)with a'{#}'placeholder alias (e.g.'con_{#}') can be retrieved individually via the expanded aliases like'con_1'/'con_2'/ ... (see Relations, Bulk Generation, and Reference Patterns)
Example: A Complete Test Workflow
Combining start → add → create → getByAlias into a single test yields:
@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);
}
Points:
- Not a single SOQL in the test body. Assertion targets are retrieved directly via
getByAlias(...) - We
.add(...)theAccountfirst here, but if we instead.add(...)theOpportunityfirst, the result would be identical — SOrchestrator re-orders things via dependency analysis - The parent Id is copied into the child by the
.use('parentAccount', 'Id', 'AccountId')declaration alone. The procedure of "insert the parent, grab its Id into a variable, assign it to the child's AccountId..." disappears
Common Pitfalls
Circular Dependencies
If you create a dependency where both A.use('B', ...) and B.use('A', ...) hold, the topological sort cannot resolve it and .create() fails with Circular or invalid reference detected. Reconsider the design — either make one side a one-way reference, or replace the parent-child link with withChildren.
Duplicate Aliases
If .alias('foo') appears in two places within the same SOrchestrator, you'll hit Duplicate alias detected. When bulking via .times(n), declare aliases with a placeholder like 'foo_{#}' so that unique aliases are issued after expansion.
Reference to a Non-Existent Alias
If you reference an alias that hasn't been declared anywhere — e.g. .use('typoAlias', ...) — the call fails at .create() with the same family of errors (Circular or invalid reference detected). Alias typos are easy to miss, so it pays to develop the habit of reviewing "the first argument of .use" alongside "its corresponding .alias" as a pair.
Don't Rely on Auto-Generated Aliases for Retrieval
Omitting .alias(...) is fine for registering a blueprint; internally it gets an auto-generated alias like __Account_0_1__. However, retrieving these via getByAlias later is impractical. If you plan to retrieve a blueprint via getByAlias, always attach an explicit .alias(...) as a rule.
Related Documents
- Declaring a Single Record with SBlueprint: How to build the blueprints you pass to
.add(...) - Relations, Bulk Generation, and Reference Patterns: Applications of
withChildren/times/{#}/{P0}{P1} - API Reference: SOrchestrator: Full method signatures
- Back to the ApexBlueprint Guide