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
| Package | Purpose | Require When |
|---|---|---|
vortos-persistence | Core interfaces and in-memory implementations | Always |
vortos-persistence-dbal | PostgreSQL/MySQL write repositories (raw SQL) | Using DBAL for writes — choose this or ORM |
vortos-persistence-orm | Doctrine ORM write repositories | Using ORM for writes — choose this or DBAL |
vortos-persistence-mongo | MongoDB read repositories | Using 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 sideORM 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 sidevortos-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_readsConfiguration
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:
return static function (VortosPersistenceConfig $config): void {
$config->writeDsn('pgsql://postgres:test@write_db:5432/myapp_test');
};Package Registration
DBAL:
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:
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:persistenceSafe to run on every deploy — all operations are idempotent.
Module Overview
Write Repositories
Implement DbalMapper, annotate with #[UsesDbalMapper], and get DbalStore injected automatically.
Read Repositories
Annotate with #[MongoCollection] and get MongoStore injected — no base class required.
Unit of Work
Transaction boundaries, connection resilience, and atomicity.
DBAL
Connection factory, DSN drivers, SSL, and advanced configuration.
Doctrine ORM
Zero-SQL aggregate persistence via Doctrine ORM.
MongoDB
Client setup, collections, indexes, and bulk operations.
Optimistic Locking
How version-based concurrency control works and how to handle conflicts.
Pagination
Cursor-based keyset pagination — O(1) performance at any depth.
Testing
In-memory repositories for fast, dependency-free tests.