Skip to content

v3.0.0

Latest

Choose a tag to compare

@pgajek2 pgajek2 released this 31 Jan 20:02

v3.0.0 - 31-January-2026

Scope

  • Automatic Record-Level Dependency Resolution
  • Performance Improvements
  • Internal Architecture Refactoring

DML

  • Automatic dependency resolution at the record level (not just SObject type level)
  • Wave-based execution ensures parents are committed before dependents
  • Optimal DML grouping minimizes the number of database operations

New Features

Automatic Record-Level Dependency Resolution

DML Lib now automatically tracks dependencies between individual records and executes them in optimized "waves". When you define relationships using withRelationship(), the library builds a dependency graph and ensures parent records are committed before their dependents.

Example: Complex Multi-Level Dependencies

Account parentAccount = new Account(Name = 'Parent');
Account childAccount = new Account(Name = 'Child');
Contact contact = new Contact(LastName = 'Smith');
Opportunity opportunity = new Opportunity(Name = 'Deal', StageName = 'New', CloseDate = Date.today());

new DML()
    .toInsert(parentAccount)
    .toInsert(DML.Record(childAccount).withRelationship(Account.ParentId, parentAccount))
    .toInsert(DML.Record(contact).withRelationship(Contact.AccountId, childAccount))
    .toInsert(DML.Record(opportunity).withRelationship(Opportunity.AccountId, childAccount))
    .commitWork();

// Execution waves:
// Wave 1: parentAccount (no dependencies)
// Wave 2: childAccount (depends on parentAccount)
// Wave 3: contact, opportunity (both depend on childAccount)

Wave-Based Execution

Records are now organized into execution waves based on their dependencies. Records in the same wave that share the same operation type and SObject type are batched into a single DML statement.

Example: Optimal DML Grouping

new DML()
    .toInsert(account1)
    .toInsert(account2)
    .toUpsert(account3)
    .toUpsert(account4)
    .toInsert(DML.Record(account5).withRelationship(Account.ParentId, account2))
    .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account2))
    .toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, account3))
    .toInsert(opportunity1)
    .toInsert(DML.Record(opportunity2).withRelationship(Opportunity.AccountId, account4))
    .toInsert(lead1)
    .commitWork();

// Result: 7 DML statements executed (optimally grouped)
DML # Operation SObject Records Reason
1 INSERT Account account1, account2 No dependencies, same bucket
2 UPSERT Account account3, account4 No dependencies, different operation
3 INSERT Opportunity opportunity1 No dependencies
4 INSERT Lead lead1 No dependencies
5 INSERT Account account5 Depends on account2
6 INSERT Contact contact1, contact2 Depend on account2 and account3
7 INSERT Opportunity opportunity2 Depends on account4

Improvements

Validation Improvements

Added null check validation when creating records by ID:

// Now throws: "Invalid argument: recordId. Record ID cannot be null."
DML.Record(null);

Documentation

  • Updated Registration documentation with detailed explanation of the dependency graph algorithm
  • Added visual diagrams showing wave-based execution
  • Added examples demonstrating optimal DML grouping

🚨 Breaking Changes 🚨

Removed: Custom Execution Order Constructor

The constructor that accepted a custom execution order has been removed:

// ❌ No longer available
new DML(new List<SObjectType>{ Account.SObjectType, Contact.SObjectType });

// ✅ Use automatic dependency resolution instead
new DML()
    .toInsert(account)
    .toInsert(DML.Record(contact).withRelationship(Contact.AccountId, account))
    .commitWork();

Migration: Remove the custom execution order parameter. The library now automatically determines the correct execution order based on defined relationships.

Changed: Dependency Detection

Previously, the library tracked dependencies at the SObject type level. Now it tracks at the record level, which provides more accurate execution ordering but may result in different execution patterns for complex scenarios.

Before (v2.x): All Accounts inserted first, then all Contacts
After (v3.x): Records inserted in waves based on actual dependencies between specific records

Internal Refactoring

  • Replaced DmlCommand classes with DmlStrategy pattern for cleaner separation of concerns
  • Split orchestration into DependencyOrchestrator (INSERT/UPSERT) and LinearOrchestrator (UPDATE/DELETE/UNDELETE/MERGE/PUBLISH)
  • Introduced StrategiesStorage for strategy instance management
  • Changed from Kahn's algorithm on SObject types to Kahn's algorithm on individual records
  • Improved RandomIdGenerator with deterministic counter-based ID generation
  • Added ProcessingGroup class for efficient record batching
  • Introduced ExecutionResult and SObjectAggregatedResult for better result handling
  • Copyright year updated to 2026