# Dependency Resolution Internals: Topological Sort + Alias Resolution

> **Who this article is for**: Developers curious about ApexBlueprint's **internal implementation**. We trace "what happens behind the simple API" in phase-by-phase pseudocode, with references to the actual implementation files (`SOrchestrator.cls` / `SBlueprintAnalyzer.cls` / `SBlueprintRealizer.cls`). For a sister article focused on the design rationale and philosophy, see [Declarative Data Specification: Why the Blueprint Form?](/apex-stem/docs/declarative-data-specification).

ApexBlueprint's API is **just eight types of methods**: `of` / `set` / `template` / `alias` / `use` / `times` / `withChildren` / `parentIdField`. And yet, with these alone, you can handle all the common difficulties of integration test data — parent-child nesting, bulk generation, sibling references, hierarchical resolution of "the true parent for me", and disambiguating multiple lookups.

This gap comes from the fact that **several independent problems are being solved at once** inside `SOrchestrator.create()`. This page digs into what happens behind the user's view, phase by phase.

## The User's View vs the Internal Processing

From the user's perspective, `.create()` looks like a single operation: "**insert every blueprint I added into the database in the correct order**".

But internally, four independent problems are being solved to make this happen:

| Problem | Core of the Solution |
|---|---|
| 1. In what order to insert so that lookups resolve | Dependency analysis + topological sort |
| 2. How to distinguish "child 1 under parent 1" from "child 1 under parent 2" | Alias resolution + auto-alias issuance |
| 3. How to hierarchically connect records bulked via `.times(...)` | Per-parent iteration + automatic parent-Id copying |
| 4. How to identify "the true parent for me" via `{P0}` / `{P1}` | Tracking the hierarchy stack + parent-reference resolution |

These are normally separate headaches, but ApexBlueprint solves them **continuously, within the same pipeline**.

## The Phases of Internal Processing

When `.create()` is called, the flow proceeds roughly as follows.

### Phase 1: Collecting the Blueprints

The `SBlueprint`s passed via `SOrchestrator.start().add(...).add(...)` are held inside SOrchestrator's list **in their addition order, first of all**. No validation or execution happens at this stage.

Child blueprints nested via `withChildren` are held as a **tree structure** inside the parent blueprint. Following the root blueprint lets you extract all children, grandchildren, and great-grandchildren below it.

### Phase 2: Building the Dependency Graph

The moment `.create()` is called, SOrchestrator first **builds a dependency graph**. Each blueprint becomes a node, and the following relationships are registered as directed edges:

- **The `withChildren` parent-child relationship**: an edge from parent → child (the child depends on the parent's Id)
- **The `.use(alias, ...)` sibling reference**: an edge from the alias source blueprint → this blueprint (this one depends on the source's value)
- **The `.after(alias)` ordering constraint**: the same alias source → this blueprint edge, except it **carries no value** and expresses ordering alone
- **Parent references like `{P0}` / `{P1}`**: an edge from the corresponding ancestor → this blueprint (the corresponding ancestor is identified during construction)

Note that detection of **duplicate aliases** and **references to non-existent aliases** happens later — they are **lazily detected** during Phase 5 (realize) based on the dependency information built here. From the user's perspective, these are still "failures at `.create()` time", but as an implementation detail, "graph construction" and "integrity validation" are kept separate.

### Phase 3: Topological Sort

Once the dependency graph is complete, SOrchestrator runs a **topological sort**, rearranging blueprints so that the depended-upon side (parents) come first.

- If a **cycle** is found in the dependencies, it fails with "`Circular or invalid reference detected`"
- Once sorting succeeds, the downstream insertion processing is guaranteed to **not have to worry about insertion order**

The reason you can write `.add(...)` calls in "the most readable order" is that the order gets reset here.

### Phase 4: Alias Resolution and Auto-Alias Issuance

As the sorted blueprints are processed in order, each gets an alias assigned.

- Aliases explicitly set via `.alias(...)` are **used as-is** (`{#}` placeholders are finalized after expansion)
- Blueprints without `.alias(...)` get an **auto-generated alias**

The auto-generated alias format is internally `__{SObjectName}_{globalCounter}_{#}__` (e.g. `__Account_0_1__` / `__Contact_1_2__`). The middle number is **a global counter assigned across all blueprints, not the hierarchy depth** (the `1` in `__Contact_1_1__` seen in test assertions is not the depth but the analysis ordering). The hierarchy is represented via the **parent prefix** explained next.

Blueprints nested via `withChildren` get the parent's alias prefixed onto theirs, yielding compound aliases like `__Account_0_1____Contact_1_1__`. This gives "child 1 under parent 1" and "child 1 under parent 2" **distinct namespaces as separate records**.

### Phase 5: Hierarchical Realize and Parent-Id Copying

Now the Realizer takes over. It walks the sorted blueprints in order and **realizes** each (= converts it into an SObject instance).

When `.times(...)` is combined with nesting, realize behaves like this:

```
Loop the parent blueprint times(N) times
  For each parent instance:
    Realize the parent as an SObject
    Loop the child blueprint times(M) times
      For each child instance:
        Realize the child as an SObject
        Copy the parent's Id into the child's lookup field
        ※ The Id is still a placeholder at this point (pre-insert)
        Recurse if there are grandchildren
```

What's important here is the property that **a fresh, full set of children is regenerated per parent instance**. This is the source of the "Multiplication: upper-tier `times` propagates downward" behavior (see [Relations, Bulk Generation, and Reference Patterns](/apex-stem/docs/apex-blueprint-relations-and-bulk)).

#### Where `{P0}` / `{P1}` Resolution Happens

Resolution of `{Pn}` happens right inside this "realize children per parent" loop. The Realizer dynamically assembles a **parent-position map** (a `Map` named `parentPositionToAlias`) as it recurses, and a child blueprint's declaration like `.use('{P1}', 'LastName', 'Subject')` is resolved as "the alias of the blueprint currently occupying tier 1 (= the true parent for me) in the parent-position map at this moment".

So `{Pn}` is **not a static alias string, but a reference to a dynamically constructed parent-position map**. As nesting deepens, more entries are added to the map, which is why the numbers `{P0}` / `{P1}` / `{P2}` can be specified consistently as **absolute depths from the root**.

You could achieve something similar by embedding the parent's alias into the alias yourself (`{P0}_child_{#}`) — that's also implementation-supported. But the user has to assemble the alias string in their head on the `.use(...)` side, which is why `{Pn}` is the **more structurally writable** design.

> The realize described here, combined with Phase 6 below, is executed **one layer at a time**. It is not "realize all blueprints first, then bulk-insert at the end" (details in Phase 6).

### Phase 6: Per-Layer Bulk DML Insert (Alternating Loop with Phase 5)

Implementation-wise, Phase 5 (realize) and Phase 6 (DML insert) are **not run end-to-end as one shot. They alternate per "layer", as derived by Phase 3**. A "layer" here is **the set of blueprints at the same depth**, as determined by dependency analysis — the blueprint closest to the root is layer 0, its children are layer 1, and so on (`SOrchestrator.BuildLayers`).

The pseudocode for `.create()` execution looks like this:

```
for each layer from 0 to maxLayer:
  // Phase 5: realize every blueprint in this layer
  //   The upper layers are already inserted, so .use(parentAlias, 'Id', ...) resolves with the parent's real Id
  layerSObjects = realize all blueprints in this layer

  // Phase 6: bulk-insert this layer's SObjects
  dmlOperator.doInsert(layerSObjects)

  // Store this layer's results in the master map aliasToSObject
  // — referenced when realizing the next layer
  aliasToSObject.putAll(thisLayerResults)
```

The reason for synchronizing between layers is that **the real Id is only fixed after the parent layer has been inserted, and that's what the next layer's children need to obtain via `.use(parentAlias, 'Id', 'AccountId')`**. If you realized everything first, parent Ids would still be placeholders when copied into children, and post-insert parent-child relationships would be broken.

It's this inter-layer synchronization that lets the user enjoy the experience: "just specify the parent Id with `.use(...)`, and the real Id from the actual DML insert ends up copied into the child".

#### Where IDmlOperator Plugs In

The `dmlOperator` at the end of each layer's `dmlOperator.doInsert(layerSObjects)` is the swap-out point for production / mock:

- Production: `DmlOperator` (runs real `insert`)
- Tests: `MockDmlOperator` (no real DML; just issues placeholder Ids)

ApexBlueprint's own tests (`SOrchestratorTest`) call `SOrchestrator.start(new MockDmlOperator())` to swap here and **verify behavior without firing real DML**.

#### Timing of Duplicate-Alias Detection

In the implementation, duplicate aliases are detected at two moments:

- **Within the same layer**: when populating `layerAliasToSObject` inside `realizeLayer`
- **Across layers**: when populating the master map `aliasToSObject` after insert

Either way, a `Duplicate alias detected` exception is thrown at runtime. This is the concrete locus of the "duplicate aliases are detected lazily" claim from Phase 2.

## Why All of This Can Be Solved at Once

Looking back at ApexBlueprint's internal processing, the independent problems — "dependency graph", "topological sort", "alias resolution", "per-parent recursive realize", "parent-reference resolution", "bulk DML" — are solved continuously within the same pipeline.

The reason these collapse, from the user's perspective, into a single operation ("just declare dependencies, and it works") is that ApexBlueprint **routes every operation through one abstraction: "the declaration of the data's final shape"**. The user writes only "what records I want to ultimately exist", and the framework takes care of "the mechanical procedure to get there".

Two points stand out as marks of good design:

1. **The API surface fits into eleven methods, so the learning cost grows linearly.** It is not the kind of API where method counts grow exponentially with each new feature
2. **The solutions to each underlying problem (topological sort / auto-alias / hierarchy-stack resolution for `{Pn}`) are independent and individually replaceable.** This keeps room for future improvements to the internal implementation

ApexBlueprint's character — "simple on the surface, powerful inside" — is built on this kind of stacked separation of solutions.

### In practice: the two v2.0.0 features added no new machinery

Point 1 above reads like an abstraction, but the v2.0.0 additions are a concrete demonstration of it. **Neither `after` nor `sharedWith` added anything to dependency resolution.**

| Added API | What it actually does inside |
|---|---|
| `.after(alias)` | Pushes a dependency with no `fromField` / `toField` onto the **same list `.use()` uses**. To the topological sort, one more edge appeared that happens to carry no value |
| `.sharedWith(user, level)` | Assembles a `__Share` sibling blueprint and wires it back with `.use(ownAlias, 'Id', 'ParentId')`. **One ordinary child node appeared** |

`sharedWith` lands exactly one layer after its parent not because sharing has bespoke ordering logic, but because **the `use()` edge makes the Phase 3 sort arrange it that way**. For the same reason it follows `times` bulk generation and `{Pn}` resolution automatically — no part of the bulk machinery was rewritten to accommodate sharing.

As long as a new feature reduces to "what edge does Phase 2 draw?", Phase 3 onward never has to change. **That structure is why the API surface grows only linearly.**

## Related Documents

- [Declarative Data Specification: Why the Blueprint Form?](/apex-stem/docs/declarative-data-specification): The sister Deep Dive on the philosophy behind why this design was chosen
- [Relations, Bulk Generation, and Reference Patterns](/apex-stem/docs/apex-blueprint-relations-and-bulk): The user-facing surface of API patterns
- [API Reference: SOrchestrator](/apex-stem/docs/apex-blueprint-api-sorchestrator): start / add / create / getByAlias and each exception
- [API Reference: SBlueprint](/apex-stem/docs/apex-blueprint-api-sblueprint): The full API of the method chain
- [Back to the ApexBlueprint Guide](/apex-stem/docs/apex-blueprint-guide)
