Skip to content

Store

In the end, the events/messages have to be saved somewhere. The library is based on doctrine dbal and offers two different store strategies.

But it is also possible to develop your own store by implementing the Store interface.

Create DBAL connection

The first thing we need for our store is a DBAL connection:

use Doctrine\DBAL\DriverManager;

$connection = DriverManager::getConnection([
    'url' => 'mysql://user:secret@localhost/app'
]);

Note

You can find out more about how to create a connection here

Store types

We offer two store strategies that you can choose as you like.

Single Table Store

With the SingleTableStore everything is saved in one table. The dbal connection is needed, a mapping of the aggregate class and aggregate name and, last but not least, the table name.

use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;
use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
use Patchlevel\EventSourcing\Store\SingleTableStore;

$store = new SingleTableStore(
    $connection,
    DefaultEventSerializer::createFromPaths(['src/Event']),
    new AggregateRootRegistry([
        'profile' => Profile::class
    ]),
    'eventstore'
);

Tip

You can switch between strategies using the pipeline.

Multi Table Store

With the MultiTableStore a separate table is created for each aggregate type. In addition, a meta table is created by referencing all events in the correct order. The dbal connection is needed, a mapping of the aggregate class and table name and, last but not least, the table name for the metadata.

use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;
use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
use Patchlevel\EventSourcing\Store\MultiTableStore;

$store = new MultiTableStore(
    $connection,
    DefaultEventSerializer::createFromPaths(['src/Event']),
    new AggregateRootRegistry([
        'profile' => Profile::class
    ]),
    'eventstore'
);

Tip

You can switch between strategies using the pipeline.

Transaction

Our stores also implement the TransactionStore interface. This allows you to combine several aggregate interactions in one transaction and thus ensure that everything is saved together or none of it.

Since the library is based on doctrine dbal, our implementation is just a proxy.

Note

You can find more about dbal transaction here.

Begin transaction

$store->transactionBegin();

Commit transaction

$store->transactionCommit();

Rollback transaction

$store->transactionRollback();

Transactional function

There is also the possibility of executing a function in a transaction. Then dbal takes care of starting a transaction, committing it and then possibly rollback it again.

$store->transactional(function () use ($command, $bankAccountRepository) {
    $accountFrom = $bankAccountRepository->get($command->from());
    $accountTo = $bankAccountRepository->get($command->to());

    $accountFrom->transferMoney($command->to(), $command->amount());
    $accountTo->receiveMoney($command->from(), $command->amount());

    $bankAccountRepository->save($accountFrom);
    $bankAccountRepository->save($accountTo);
});

Tip

To ensure that all listeners are executed for the released events or that the listeners are not executed if the transaction fails, you can use the outbox pattern for it.

Schema

With the help of the SchemaDirector, the database structure can be created, updated and deleted.

Tip

You can also use doctrine migration to create and keep your schema in sync.

Create SchemaDirector

use Patchlevel\EventSourcing\Schema\DoctrineSchemaDirector;

$schemaDirector = new DoctrineSchemaDirector(
    $connection,
    $store
);

Create schema

$schemaDirector->create();

Update schema

$schemaDirector->update();

Drop schema

$schemaDirector->drop();