Skip to content

Build, persist, release and query

Create a build

$strate = $stratifiedPersist->createNewVersion(
    [BookingItemManager::class],
    scope: 'booking',
    scopeId: 42,
);

createNewStrate() is an alias of createNewVersion(). The method creates a UUID v7 row in stratified_build with status building.

At this point the candidate exists, but released queries still point to the previous snapshot.

Persist snapshot rows

/** @var BookingItemEntity $item */
$item = $entityManagerFactory->get(BookingItemManager::class)->newEntity();
$item
    ->setId($uuid)
    ->setBookingId(42)
    ->setSku('BIKE-001');

$stratifiedPersist->persist($item, $strate);
$stratifiedPersist->persistMany($otherItems, $strate);

The service assigns buildStrate immediately before entity persistence. It does not verify that the entity scope-id value matches the build metadata, so the caller must construct a consistent snapshot.

persistMany() iterates over the supplied entities; it is not an automatic parallel executor.

Parallelize independent persistence work

The main performance advantage of the architecture is that reader isolation no longer requires every graph write to be serialized inside one long transaction.

If several branches do not depend on one another, the application may persist them concurrently under the same unreleased strate:

build B
  |
  +-- branch A: products ---------+
  +-- branch B: tags -------------+-- concurrent where dependencies allow
  +-- branch C: settings ---------+
  |
  +-- dependent join rows after their parents exist
  |
  +-- release B only after every required branch succeeds

This can reduce elapsed persistence time toward the duration of the critical dependency path instead of the sum of all independent branches. Actual gains depend on connection-pool size, database capacity, indexes, constraints and contention.

Use transactions inside a branch when local ACID guarantees are required. Strates removes the need to use one long transaction solely to hide partially built state from readers.

See Architecture, versioning and parallel persistence for the full model.

Release

$stratifiedPersist->release('booking', 42, $strate);

Release performs these checks and writes:

  1. the scope must be configured;
  2. the exact build (scope, scopeId, strate) must exist;
  3. status must be building or already released;
  4. the release pointer is inserted or updated;
  5. the build status becomes released.

Calling release again for the same build is idempotent.

The release pointer is the source of truth for reader visibility. Business rows are not rewritten during publication.

For transaction-capable drivers, release metadata uses the normal PersistThread::commit() path. With the native PostgreSQL driver used by Core 3.0, Strates deliberately calls flush() directly because the core commit fallback is not a native PostgreSQL transaction path. Do not treat the PostgreSQL metadata flush as a database transaction; the snapshot visibility model still relies on the single release pointer.

Query the released snapshot

$items = $stratifiedPersist->findReleasedByScopeId('booking', 42);

For additional predicates or ordering:

$query = $stratifiedPersist->createReleasedQueryBuilder(
    scope: 'booking',
    alias: 'item',
    scopeIdValue: 42,
);

$query->addOrderBy('sku', 'item');
$items = $entityManagerFactory
    ->get(BookingItemManager::class)
    ->getResult($query);

The generated query always filters on both the configured scope-id field and the released buildStrate. When no release pointer exists, a sentinel strate is used and the result is empty.

Mark a failed build

try {
    // Build the complete candidate snapshot.
    $stratifiedPersist->release('booking', 42, $strate);
} catch (Throwable $exception) {
    $stratifiedPersist->markBuildAsFailed($strate);
    throw $exception;
}

A failed candidate never replaces the current release pointer, so readers continue to use the previously released state.

Next chapter: Stratified relation graphs