Skip to content

Commit

Permalink
Merge pull request #225 from patchlevel/copy-connection-for-projection
Browse files Browse the repository at this point in the history
copy connection for projection
  • Loading branch information
DavidBadura authored Dec 10, 2024
2 parents d4fe199 + 115722b commit 799bed4
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 14 deletions.
74 changes: 69 additions & 5 deletions docs/pages/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,30 @@ patchlevel_event_sourcing:
You can find out more about how to create a connection
[here](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)

### Connection for Projections

Per default, our event sourcing connection is not available to use in your application.
But you can create a dedicated connection that you can use for your projections.

```yaml
patchlevel_event_sourcing:
connection:
url: '%env(EVENTSTORE_URL)%'
provide_dedicated_connection: true
```
!!! tip

You can autowire the connection in your services like this:

```php
use Doctrine\DBAL\Connection;
public function __construct(
private readonly Connection $projectionConnection,
) {
}
```

### Doctrine Bundle

If you have installed the [doctrine bundle](https://github.com/doctrine/DoctrineBundle),
Expand All @@ -108,6 +132,11 @@ patchlevel_event_sourcing:
connection:
service: doctrine.dbal.eventstore_connection
```
!!! danger

Do not use the same connection for event sourcing and your projections,
otherwise you may run into transaction problems.

!!! warning

If you want to use the same connection as doctrine orm, then you have to set the flag `merge_orm_schema`.
Expand All @@ -118,6 +147,42 @@ patchlevel_event_sourcing:
You can find out more about the dbal configuration
[here](https://symfony.com/bundles/DoctrineBundle/current/configuration.html).

If you are using Doctrine for your projections too, you need to create a dedicated connection for this.
You can do this by defining a new connection named `projection` in the `doctrine.yaml` file
and use the same connection url as for the event store.

```yaml
doctrine:
dbal:
connections:
eventstore:
url: '%env(EVENTSTORE_URL)%'
projection:
url: '%env(EVENTSTORE_URL)%'
patchlevel_event_sourcing:
connection:
service: doctrine.dbal.eventstore_connection
```
Then you can use this connection in your projections.
If you are using autowiring you can inject the right connection `Connection $projectionConnection` parameter name.
The prefix `projection` is used to identify the connection.

```php
namespace App\Projection;
use Doctrine\DBAL\Connection;
use Patchlevel\EventSourcing\Attribute\Projector;
#[Projector('my_projection')]
class MyProjection
{
public function __construct(
private readonly Connection $projectionConnection,
) {
}
}
```
## Store

The store and schema is configurable.
Expand Down Expand Up @@ -200,12 +265,12 @@ patchlevel_event_sourcing:

You can find out more about subscriptions in the library
[documentation](https://event-sourcing.patchlevel.io/latest/subscription/).

### Store

You can change where the subscription engine stores its necessary information about the subscription.
Default is `dbal`, which means it stores it in the same DB that is used by the dbal event store.
Otherwise you also have the option to set it to `in_memory`, then this information will not be persisted anywhere.
You can change where the subscription engine stores its necessary information about the subscription.
Default is `dbal`, which means it stores it in the same DB that is used by the dbal event store.
Otherwise you also have the option to set it to `in_memory`, then this information will not be persisted anywhere.
This is very useful for testing. And if that is not enough, you can also define a `custom` store and specify the service.

```yaml
Expand All @@ -215,7 +280,6 @@ patchlevel_event_sourcing:
type: 'custom' # default is 'dbal'
service: 'my_subscription_store'
```

### Catch Up

If aggregates are used in the processors and new events are generated there,
Expand Down
5 changes: 3 additions & 2 deletions docs/pages/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@ final class HotelProjection
{
use SubscriberUtil;

public function __construct(private Connection $db)
{
public function __construct(
private Connection $projectionConnection,
) {
}

/** @return list<array{id: string, name: string, guests: int}> */
Expand Down
8 changes: 3 additions & 5 deletions docs/pages/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ patchlevel_event_sourcing:
events: '%kernel.project_dir%/src'
connection:
url: '%env(EVENTSTORE_URL)%'
provide_dedicated_connection: true

when@dev:
patchlevel_event_sourcing:
Expand Down Expand Up @@ -63,7 +64,7 @@ EVENTSTORE_URL="pdo-pgsql://app:[email protected]:5432/app?serverVersion=16&c
!!! note

You can find out more about what a connection url looks like [here](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url).

## Database with Docker

If you are using docker, you can use the following `compose.yaml` file to start a postgres database.
Expand All @@ -85,7 +86,6 @@ services:
volumes:
eventstore_data:
```
And for development, you can add a `compose.override.yaml` file to expose the port.

```yaml
Expand All @@ -94,18 +94,16 @@ services:
ports:
- "5432"
```

## Symfony CLI

If you are using the [symfony cli](https://symfony.com/download),
If you are using the [symfony cli](https://symfony.com/download),
you can configure that the database is started automatically if you start the server.
For this you have to add the following configuration to the `.symfony.local.yaml` file.

```yaml
workers:
docker_compose: ~
```

!!! success

You have successfully installed the bundle. Now you can start with the [quickstart](./getting_started.md) to get a feeling for the bundle.
Expand Down
7 changes: 6 additions & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
* },
* rebuild_after_file_change: bool
* },
* connection: ?array{service: ?string, url: ?string},
* connection: ?array{
* service: ?string,
* url: ?string,
* provide_dedicated_connection: bool
* },
* store: array{merge_orm_schema: bool, options: array<string, mixed>, type: string, service: ?string},
* aggregates: list<string>,
* events: list<string>,
Expand Down Expand Up @@ -57,6 +61,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->children()
->scalarNode('service')->defaultNull()->end()
->scalarNode('url')->defaultNull()->end()
->booleanNode('provide_dedicated_connection')->defaultFalse()->end()
->end()
->end()

Expand Down
16 changes: 15 additions & 1 deletion src/DependencyInjection/PatchlevelEventSourcingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -470,11 +470,25 @@ private function configureConnection(array $config, ContainerBuilder $container)
$config['connection']['url'],
]);

if ($config['connection']['provide_dedicated_connection']) {
$container->register('event_sourcing.dbal_public_connection', Connection::class)
->setFactory([DbalConnectionFactory::class, 'createConnection'])
->setArguments([
$config['connection']['url'],
]);

$container->setAlias(Connection::class, 'event_sourcing.dbal_public_connection');
}

return;
}

if ($config['connection']['service'] === null) {
return;
throw new InvalidArgumentException('Connection service or url is required');
}

if ($config['connection']['provide_dedicated_connection']) {
throw new InvalidArgumentException('Providing dedicated connection is only possible with url');
}

$container->setAlias('event_sourcing.dbal_connection', $config['connection']['service']);
Expand Down
24 changes: 24 additions & 0 deletions tests/Unit/PatchlevelEventSourcingBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,30 @@ public function testConnectionService(): void
self::assertInstanceOf(DoctrineDbalStore::class, $container->get(Store::class));
}

public function testProjectionConnection(): void
{
$container = new ContainerBuilder();
$this->compileContainer(
$container,
[
'patchlevel_event_sourcing' => [
'connection' => [
'url' => 'sqlite3:///:memory:',
'provide_dedicated_connection' => true,
],
],
]
);

$eventSourcingConnection = $container->get('event_sourcing.dbal_connection');
$projectionConnection = $container->get('event_sourcing.dbal_public_connection');

self::assertInstanceOf(Connection::class, $eventSourcingConnection);
self::assertInstanceOf(Connection::class, $projectionConnection);

self::assertNotSame($eventSourcingConnection, $projectionConnection);
}

public function testCustomStore(): void
{
$store = $this->prophesize(Store::class)->reveal();
Expand Down

0 comments on commit 799bed4

Please sign in to comment.