Vortos
Persistence

Persistence

Repository pattern with DBAL or ORM write repositories, MongoDB read repositories, optimistic locking, and cursor-based pagination.

Persistence

Vortos uses a CQRS-aligned persistence model. Write operations go through either DBAL-backed or Doctrine ORM repositories, depending on your package choice. Read operations go through MongoDB-backed repositories. Domain aggregates never depend on any persistence technology — only the interfaces defined in vortos-domain are visible to application code.

Architecture

Write Side                        Read Side
──────────────────────────────    ──────────────────────────────
Command → Handler                 Query → Handler
    │                                 │
    ▼                                 ▼
UserRepositoryInterface           UserReadRepositoryInterface
    │                                 │
    ▼                                 ▼
UserWriteRepository               UserReadRepository
  #[UsesDbalMapper]                  #[MongoCollection]
  or #[UsesOrmEntity]                   │
    │                                 ▼
    ▼                          MongoStore (injected at compile time)
DbalStore / OrmStore                  │
(injected at compile time)            ▼
    │                         MongoDB (read-optimised projections)

PostgreSQL (source of truth)

The write side is transactional and strongly consistent. The read side is eventually consistent — projections update MongoDB asynchronously when domain events are processed.

Repository interfaces are pure domain contracts — no framework imports, no extends. Switching write backends (DBAL ↔ ORM) requires no changes to command handlers, domain services, or tests that use in-memory repositories.

Packages

PackagePurposeRequire When
vortos-persistenceCore interfaces and in-memory implementationsAlways
vortos-persistence-dbalPostgreSQL/MySQL write repositories (raw SQL)Using DBAL for writes — choose this or ORM
vortos-persistence-ormDoctrine ORM write repositoriesUsing ORM for writes — choose this or DBAL
vortos-persistence-mongoMongoDB read repositoriesUsing MongoDB for reads

Choose One Write Backend

Do not install both vortos-persistence-dbal and vortos-persistence-orm. Both register UnitOfWorkInterface and Connection::class — installing both causes one to shadow the other. Choose one and use it throughout the application.

Installation

vortos-persistence is always required. Install one write backend and the read backend for your stack:

DBAL path (raw SQL writes):

composer require vortos/vortos-persistence
composer require vortos/vortos-persistence-dbal   # write side — raw SQL
composer require vortos/vortos-persistence-mongo  # read side

ORM path (Doctrine ORM writes):

composer require vortos/vortos-persistence
composer require vortos/vortos-persistence-orm    # write side — Doctrine ORM
composer require vortos/vortos-persistence-mongo  # read side

vortos-persistence-dbal and vortos-persistence-orm are listed in suggest in the framework meta-package. The setup wizard installs the correct one automatically when you choose a write database option during php vortos setup.

Environment Variables

VORTOS_WRITE_DB_DSN=pgsql://postgres:secret@write_db:5432/myapp
VORTOS_READ_DB_DSN=mongodb://root:secret@read_db:27017
VORTOS_READ_DB_NAME=myapp_reads

Configuration

config/persistence.php
use Vortos\Persistence\DependencyInjection\VortosPersistenceConfig;

return static function (VortosPersistenceConfig $config): void {
    $config
        ->writeDsn($_ENV['VORTOS_WRITE_DB_DSN'])
        ->readDsn($_ENV['VORTOS_READ_DB_DSN'])
        ->readDatabase($_ENV['VORTOS_READ_DB_NAME']);
};

Environment-specific overrides:

config/test/persistence.php
return static function (VortosPersistenceConfig $config): void {
    $config->writeDsn('pgsql://postgres:test@write_db:5432/myapp_test');
};

Package Registration

DBAL:

bootstrap/app.php
use Vortos\Persistence\DependencyInjection\PersistencePackage;
use Vortos\PersistenceDbal\DependencyInjection\DbalPersistencePackage;
use Vortos\PersistenceMongo\DependencyInjection\MongoPersistencePackage;

$packages = [
    new PersistencePackage(),       // always first — sets DSN parameters
    new DbalPersistencePackage(),   // write side
    new MongoPersistencePackage(),  // read side
];

ORM:

bootstrap/app.php
use Vortos\Persistence\DependencyInjection\PersistencePackage;
use Vortos\PersistenceOrm\DependencyInjection\PersistenceOrmPackage;
use Vortos\PersistenceMongo\DependencyInjection\MongoPersistencePackage;

$packages = [
    new PersistencePackage(),       // always first — sets DSN parameters
    new PersistenceOrmPackage(),    // write side
    new MongoPersistencePackage(),  // read side
];

Load Order Matters

PersistencePackage must be registered before the write and read persistence packages. The DBAL, ORM, and Mongo extensions read container parameters that PersistenceExtension sets — wrong order causes a compile-time error.

Setup Command

After deploying, run once to ensure MongoDB indexes exist:

php bin/console vortos:setup:persistence

Safe to run on every deploy — all operations are idempotent.

Module Overview

On this page