diff --git a/doc/bookdown.json b/doc/bookdown.json
index d570198..1a2f5e9 100644
--- a/doc/bookdown.json
+++ b/doc/bookdown.json
@@ -1,12 +1,15 @@
{
"title": "prooph event-store Symfony bundle",
"content": [
- {"getting-started": "book/getting-started/bookdown.json"},
- {"reference": "book/reference/bookdown.json"},
- {"contribute": "book/contribute/bookdown.json"}
+ {"intro": "../README.md"},
+ {"getting_started": "getting_started.md"},
+ {"event_store": "event_store.md"},
+ {"projection_manager": "projection_manager.md"},
+ {"event_store_bus_bridge": "event_store_bus_bridge.md"},
+ {"configuration_reference": "configuration_reference.md"}
],
"target": "./html",
"tocDepth": 2,
- "template": "../vendor/tobiju/bookdown-bootswatch-templates/templates/main.php",
+ "template": "../vendor/prooph/bookdown-template/templates/main.php",
"copyright": "Copyright (c) 2016 - 2019 prooph maintainers
Powered by Bookdown Bootswatch Templates"
}
diff --git a/doc/configuration_reference.md b/doc/configuration_reference.md
new file mode 100644
index 0000000..2579d21
--- /dev/null
+++ b/doc/configuration_reference.md
@@ -0,0 +1,131 @@
+# Configuration Reference
+
+```yaml
+prooph_event_store:
+ stores:
+ acme_store:
+ event_emitter: Prooph\Common\Event\ProophActionEventEmitter
+ wrap_action_event_emitter: true
+ event_store: Prooph\EventStore\Pdo\MysqlEventStore
+ repositories:
+ todo_list:
+ repository_class: Prooph\ProophessorDo\Infrastructure\Repository\EventStoreUserCollection
+ aggregate_type: Prooph\ProophessorDo\Model\User\User
+ aggregate_translator: prooph_event_sourcing.aggregate_translator
+ snapshot_store: ~
+ stream_name: ~
+ one_stream_per_aggregate: false
+ projection_managers:
+ main_manager:
+ event_store: Prooph\EventStore\Pdo\MysqlEventStore
+ connection: 'doctrine.pdo.connection'
+ event_streams_table: 'event_streams'
+ projection_table: 'projections'
+ projections:
+ user_projection:
+ read_model: Prooph\ProophessorDo\Projection\User\UserReadModel
+ projection: Prooph\ProophessorDo\Projection\User\UserProjection
+```
+
+## stores
+
+*Optional*
+
+This section contains the configuration of your event stores.
+Please have a look at [the event store section](./event_store.md) of this documentation for further details.
+The name of the event store will be part of its service id: `prooph_event_store.`.
+For the `acme_store` in our example above it will be `prooph_event_store.acme_store`.
+
+### event_emitter
+
+*Optional*
+
+The event emitter that is used by the ActionEventEmitterEventStore.
+It must be a class that implements `Prooph\Common\Event\ActionEventEmitter`.
+The default value should be fine for most use cases.
+
+
+### wrap_action_event_emitter
+
+*Optional*
+
+Should the given event store be decorated by an ActionEventEmitterEventStore?
+In most cases you should keep this with the default value `true`.
+
+
+### event_store
+
+*Required*
+
+The id of a service whose class implements `Prooph\EventStore\EventStore`.
+Please have a look at [the event store section](./event_store.md) of this documentation for further details.
+
+
+### repositories
+
+*Optional*
+
+Defines the repository that you can use to load and store your aggregates.
+For further details please have a look at [the event store section](./event_store.md) of this documentation.
+
+
+#### repository_class
+
+*Required*
+
+The FQCN of the repository.
+In most cases it will be a subclass of `Prooph\EventSourcing\Aggregate\AggregateRepository` but it most not be,
+it must just accept the same arguments in the constructor.
+
+
+#### aggregate_type
+
+*Required*
+
+The FQCN of the aggregate that is loaded and stored by the repository.
+
+
+## aggregate_translator
+
+*Required*
+
+The service id of the aggregate translator that is used by the repository.
+Its class must implement `Prooph\EventSourcing\Aggregate\AggregateTranslator`.
+
+#### snapshot_store
+
+*Optional*
+
+The service id of a snap shot store.
+Its class must implement `Prooph\SnapshotStore\SnapshotStore`.
+
+#### stream_name
+
+*Optional*
+
+You can pass a string as custom stream name if you want.
+
+
+#### one_stream_per_aggregate
+
+*Optional*
+
+Should the repository create an own single stream for each aggregate?
+See section *Using different Stream Strategies* for of [the event store section](./event_store.md) of this documentation for further details.
+
+
+## projection_managers
+
+### event_store
+
+### connection
+
+### event_streams_table
+
+### projection_table
+
+### projections
+
+#### read_model
+
+#### projection
diff --git a/doc/event_store.md b/doc/event_store.md
new file mode 100644
index 0000000..292293b
--- /dev/null
+++ b/doc/event_store.md
@@ -0,0 +1,284 @@
+# Event Store
+
+This documentation covers just the configuration of Prooph Event Stores in Symfony.
+To inform yourself about the Prooph Event Store, please have a look at its
+[official documentation](http://docs.getprooph.org/event-store/).
+
+## Setting up a MySQL PDO Event Store
+
+To setup a MySQL PDO Event Store we need the `prooph/pdo-event-store` package.
+
+```bash
+composer require prooph/pdo-event-store
+```
+
+> **Hint**: You can also follow this instruction if you want to setup a PDO Event Store for MariaDB or PostgreSQL.
+> You just need to use other classes which are also part of the `prooph/pdo-event-store` package.
+> For further details please have a look at the [prooph/pdo-event-store package](https://github.com/prooph/pdo-event-store).
+
+Before we setup our event store, we need to setup some services:
+
+```yaml
+# app/config/services.yml or (flex) config/packages/prooph_event_store.yaml
+services:
+ prooph_event_store.pdo_mysql_event_store:
+ class: Prooph\EventStore\Pdo\MySqlEventStore
+ arguments:
+ - '@prooph_event_store.message_factory'
+ - '@pdo.connection'
+ - '@prooph_event_store.mysql.single_stream_strategy'
+
+ prooph_event_store.mysql.single_stream_strategy:
+ class: Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy
+
+ pdo.connection:
+ class: PDO
+ arguments: ['%dsn%']
+```
+
+> **Hint**: For reusing a PDO connection from Doctrine please see below.
+
+> **Hint**: You can also use other stream strategies.
+> Have a look at the documentation of the [prooph/pdo-event-store package](https://github.com/prooph/pdo-event-store/blob/master/docs/variants.md)
+> to learn about the different strategies.
+> See below for further information within this bundle.
+
+Do not be confused about the fact that the we defined a service with a class called event store – we are not done yet.
+But we are ready to configure the event store:
+
+```yaml
+# app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+prooph_event_store:
+ stores:
+ acme_store:
+ event_store: 'prooph_event_store.pdo_mysql_event_store'
+```
+
+> **Hint**: To get autocompletion in some IDEs you can prepend the service id
+> with an `@` (`'@prooph_event_store.pdo_mysql_event_store'`).
+>
+> The bundle will recognize this and find your event store anyway.
+
+We configured our first event store.
+To put data into the event store (and read them from it) we might want to add repositories.
+
+## Adding repositories to the event store
+
+If you are adding repositories to your event_store, you want to go for event sourcing.
+Therefore we will explain how to add an event sourced repository.
+
+First you need to install another package, prooph/event-sourcing](http://docs.getprooph.org/event-sourcing/):
+
+```bash
+composer require prooph/event-sourcing
+```
+
+Before we can start adding our repositories, we need to define another service:
+
+```yaml
+# app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+services:
+ prooph_event_sourcing.aggregate_translator:
+ class: Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator
+```
+
+It will help our repository to translate the event stream into an aggregate and vice versa.
+
+We assume that there is
+ - a class `Acme\Prooph\Repository\EventStoreUserRepository` which extends `Prooph\EventSourcing\Aggregate\AggregateRepository`
+ - and a class `Acme\Model\User` which extends `Prooph\EventSourcing\AggregateRoot`.
+
+More information about Aggregate Roots and Aggregate Repositories can be found in the [official documentation](http://docs.getprooph.org/event-sourcing/).
+
+Now we can configure our repository:
+
+```yaml
+# app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+prooph_event_store:
+ stores:
+ acme_store:
+ event_store: 'prooph_event_store.pdo_mysql_event_store'
+ repositories:
+ Acme\Prooph\Repository\EventStoreUserRepository:
+ aggregate_type: Acme\Model\User
+ aggregate_translator: 'prooph_event_sourcing.aggregate_translator'
+```
+
+> **Hint**: To get autocompletion in some IDEs you can prepend the service id
+> with an `@` (`'@prooph_event_sourcing.aggregate_translator'`).
+>
+> The bundle will recognize this and find your event store anyway.
+
+Now you can access the repository from the service container with the id `Acme\Prooph\Repository\EventStoreUserRepository`.
+
+> **Hint**: If you do not want your repositories to have their classes as service ids,
+> you can configure them like this:
+> ```yaml
+> # app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+> prooph_event_store:
+> stores:
+> acme_store:
+> event_store: 'prooph_event_store.pdo_mysql_event_store'
+> repositories:
+> acme.repository.prooph.event_store_user_repository:
+> repository_class: Acme\Prooph\Repository\EventStoreUserRepository
+> aggregate_type: Acme\Model\User
+> aggregate_translator: 'prooph_event_sourcing.aggregate_translator'
+> ```
+> This way your repository will be accessible with the service id `acme.repository.prooph.event_store_user_repository`.
+
+## Reusing a PDO connection from Doctrine
+
+If you already have a PDO connection configured through doctrine
+and you want to use the same connection for your event store,
+there is a simple way to reuse it:
+
+```yaml
+# app/config/services.yaml
+services:
+ prooph_event_store.connection.doctrine_pdo_connection:
+ class: PDO
+ factory: ['@doctrine.dbal.default_connection', getWrappedConnection]
+```
+
+## Using different Stream Strategies
+
+To make yourself familiar with different stream strategies,
+please have a look at the documentation of the [prooph/pdo-event-store package](https://github.com/prooph/pdo-event-store/blob/master/docs/variants.md).
+
+If you want to use one of the Single Stream Strategies, you just need to set up the Strategy as a service and pass it to the event store:
+
+
+```yaml
+# app/config/services.yml or (flex) config/packages/prooph_event_store.yaml
+services:
+ prooph_event_store.pdo_mysql_event_store:
+ class: Prooph\EventStore\Pdo\MySqlEventStore
+ arguments:
+ - '@prooph_event_store.message_factory'
+ - '@pdo.connection'
+ - '@prooph_event_store.mysql.single_stream_strategy'
+
+ prooph_event_store.mysql.single_stream_strategy:
+ class: Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy
+
+ pdo.connection:
+ class: PDO
+ arguments: ['%dsn%']
+```
+
+If you want to use one of the Aggregate Stream Strategies,
+you also need to configure your Stream Strategie as a service like above.
+But you also need to your repositories to follow this strategy using the `one_stream_per_aggregate` option:
+
+```yaml
+# app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+prooph_event_store:
+ stores:
+ acme_store:
+ event_store: 'prooph_event_store.pdo_mysql_event_store'
+ repositories:
+ Acme\Prooph\Repository\EventStoreUserRepository:
+ aggregate_type: Acme\Model\User
+ aggregate_translator: 'prooph_event_sourcing.aggregate_translator'
+ one_stream_per_aggregate: true
+```
+
+Because this option defaults to `false`, this is only necessary for Aggregate Stream Strategies.
+
+## Plugins
+
+A prooph Event Store can be expanded using plugins.
+If you want to know more about Event Store Plugins, please have a look at the [official documentation](http://docs.getprooph.org/event-store/event_store_plugins.html).
+
+Adding plugins to an Event Store is really simple.
+Let's assume that we already have a class implementing `Prooph\EventStore\Plugin\Plugin`
+and that we have configured it as service:
+
+```yaml
+# app/config/services.yml
+services:
+ acme.prooph.plugins.example_plugin:
+ class: Acme\Prooph\Plugins\ExamplePlugin
+```
+
+To attaching the plugin to an Event Store, we just need to tag it with `prooph_event_store..plugin`.
+So if our Event Store is named `acme_store`, it would look like this:
+
+```yaml
+# app/config/services.yml
+services:
+ acme.prooph.plugins.example_plugin:
+ class: Acme\Prooph\Plugins\ExamplePlugin
+ tags:
+ - { name: 'prooph_event_store.acme_store.plugin' }
+```
+
+If you cant to attach to plugin to multiple Event Stores, just tag it multiple times.
+In the special case that you want to attach the plugin to **every** Event Store,
+you can use the tag `prooph_event_store.plugin` instead.
+
+## Metadata enricher
+
+If you do not know what a metadata enricher is, please have a look at the official documentation of [Metadata enricher](http://docs.getprooph.org/event-store/event_store_plugins.html#3-3-4).
+
+Let's assume that we want to add the issuer of an event to the metadata.
+Our Metadata enricher might look like this:
+
+```php
+tokens = $tokens;
+ }
+
+ public function enrich(Message $message): Message
+ {
+ if ($this->tokens->getToken()) {
+ $message = $message
+ ->withAddedMetadata('issuer_type', 'user')
+ ->withAddedMetadata('issuer_name', $this->tokens->getToken()->getUsername());
+ }
+ return $message;
+ }
+}
+```
+
+And our service definition like this:
+
+```yaml
+# app/config/services.yml
+services:
+ acme.prooph.metadata_enricher.issuer:
+ class: Acme\Prooph\MetadataEnricher\IssuerMetadataEnricher
+ arguments: ['@security.token_storage']
+```
+
+To enable the enricher for every Event Store, we just need to tag the service:
+
+```yaml
+# app/config/services.yml
+services:
+ acme.prooph.metadata_enricher.issuer:
+ class: Acme\Prooph\MetadataEnricher\IssuerMetadataEnricher
+ arguments: ['@security.token_storage']
+ tags:
+ - { name: 'prooph_event_store.metadata_enricher' }
+```
+
+But be careful, this tag would add the metadata enricher to **every** Event Store.
+If you want to add it only to one store, you need to use the tag `prooph_event_store..metadata_enricher`.
diff --git a/doc/event_store_bus_bridge.md b/doc/event_store_bus_bridge.md
new file mode 100644
index 0000000..bd9ee57
--- /dev/null
+++ b/doc/event_store_bus_bridge.md
@@ -0,0 +1,64 @@
+# Event Store Bus Bridge
+
+While both the Prooph Event Store Symfony Bundle and the [Prooph Service Bus Symfony Bundle](https://github.com/prooph/service-bus-symfony-bundle/)
+are useful on its own, usually you want to use both together.
+
+To combine them, you will need the [Prooph Event Store Bus Bridge](https://github.com/prooph/event-store-bus-bridge)
+that can be required with composer:
+
+```bash
+composer require prooph/event-store-bus-bridge
+```
+
+There are three locations where we can combine both bundles.
+
+## Transaction Manager
+
+The transaction manager starts a new event store transaction on every command dispatch and commits its afterwards.
+To enable it for an Event Store, you need to add a service and tag it as a plugin for a service bus.
+
+Assuming that we have an Event Store named `acme_store` and a Service Bus named `acme_command_bus`
+the configuration might look like this:
+
+```yaml
+# app/config/services.yml
+services:
+ prooph_event_store_bus_bridge.acme_transaction_manager:
+ class: Prooph\EventStoreBusBridge\TransactionManager
+ arguments: ['@prooph_event_store.acme_store']
+ tags:
+ - { name: 'prooph_service_bus.acme_command_bus.plugin' }
+```
+
+## Event Publisher
+
+The Event Publisher is an Event Store Plugin which listens on the Event Store and publishes recorded message on the Event Bus.
+
+Assuming that we have an Event Store named `acme_store` and an Event Bus named `acme_event_bus`
+the configuration might look like this:
+
+```yaml
+# app/config/services.yml
+services:
+ prooph_event_store_bus_bridge.acme_event_publisher:
+ class: Prooph\EventStoreBusBridge\EventPublisher
+ arguments: ['@prooph_service_bus.acme_event_bus']
+ tags:
+ - { name: 'prooph_event_store.acme_store.plugin' }
+```
+
+## Causation Metadata Enricher
+
+The Causation Metadata Enricher will add causation metadata to each recorded event.
+
+To enable it for all command buses and all event stores you can use a configuration like this:
+
+```yaml
+# app/config/services.yml
+services:
+ prooph_event_store_bus_bridge.causation_metadata_enricher:
+ class: Prooph\EventStoreBusBridge\CausationMetadataEnricher
+ tags:
+ - { name: 'prooph_service_bus.command_bus.plugin' }
+ - { name: 'prooph_event_store.plugin' }
+```
diff --git a/doc/getting_started.md b/doc/getting_started.md
new file mode 100644
index 0000000..a2777c3
--- /dev/null
+++ b/doc/getting_started.md
@@ -0,0 +1,49 @@
+# Getting started
+
+This documentation covers just the configuration of the Prooph Event Store in Symfony.
+To inform yourself about the Event Store please have a look at the
+[official documentation](http://docs.getprooph.org/event-store/).
+
+## Download the Bundle
+
+Download the bundle using composer by running
+```bash
+composer require prooph/event-store-symfony-bundle
+```
+at the root of your Symfony project.
+
+## Enable the Bundle
+
+To start using this bundle, register the bundle in your application's kernel class:
+```php
+ ['all' => true],
+];
+```
+
+Now that you have installed the bundle you might want start with configuring an [Event Store](./event_store.html).
diff --git a/doc/projection_manager.md b/doc/projection_manager.md
new file mode 100644
index 0000000..a20cf45
--- /dev/null
+++ b/doc/projection_manager.md
@@ -0,0 +1,255 @@
+# Projection Manager
+
+Projection Managers will help you to create persistent projections from your event stream.
+For further information please have a look at the [official documentation](http://docs.getprooph.org/event-store/projections.html).
+
+Before you can setup an Projection Manager, you need to setup at least one [Event Store](./event_store.html).
+
+Then you can add a Projection Manager:
+
+```yaml
+# app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+prooph_event_store:
+ projection_managers:
+ acme_projection_manager:
+ event_store: 'prooph_event_store.pdo_mysql_event_store'
+ connection: 'pdo.connection'
+```
+
+Currently the bundle is limited to support Projection Managers for Event Stores
+that are either part of the [prooph/pdo-event-store package](https://github.com/prooph/pdo-event-store)
+or an `Prooph\EventStore\InMemoryEventStore`.
+In the latter case you can omit the `connection`.
+
+Because a projection manager is worthless without connections he can manage, let's configure some projections.
+
+## Configure a projection
+
+To configure a projection we need a Projection before.
+
+Projections might either implement `Prooph\Bundle\EventStore\Projection`
+or implement `Prooph\Bundle\EventStore\Projection\ReadModelProjection`.
+
+Both interfaces have just one method to configure the projection as explained in the [Event Store documentation](http://docs.getprooph.org/event-store/projections.html).
+
+To give one example from [proophessor-do-symfony](https://github.com/prooph/proophessor-do-symfony)
+here is a Read Model:
+
+```php
+connection = $connection;
+ }
+
+ public function init(): void
+ {
+ $sql = <<connection->executeQuery($sql);
+ }
+
+ public function isInitialized(): bool
+ {
+ $statement = $this->connection->executeQuery('SHOW TABLES LIKE read_todo;');
+ return $statement->fetch() !== false;
+ }
+
+ public function reset(): void
+ {
+ $this->connection->executeQuery('TRUNCATE TABLE read_todo;');
+ }
+
+ public function delete(): void
+ {
+ $this->connection->executeQuery('DROP TABLE read_todo;');
+ }
+
+ protected function insert(array $data): void
+ {
+ $this->connection->insert('read_todo', $data);
+ }
+
+ protected function update(array $data, array $identifier): void
+ {
+ $this->connection->update('read_todo', $data, $identifier);
+ }
+}
+```
+
+and the projection that uses the ReadModel:
+
+```php
+fromStream('event_stream')
+ ->when([
+ TodoWasPosted::class => function ($state, TodoWasPosted $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack('insert', [
+ 'id' => $event->todoId()->toString(),
+ 'assignee_id' => $event->assigneeId()->toString(),
+ 'text' => $event->text()->toString(),
+ 'status' => $event->todoStatus()->toString(),
+ ]);
+ },
+ TodoWasMarkedAsDone::class => function ($state, TodoWasMarkedAsDone $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack(
+ 'update',
+ ['status' => $event->newStatus()->toString()],
+ ['id' => $event->todoId()->toString()]
+ );
+ },
+ TodoWasReopened::class => function ($state, TodoWasReopened $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack(
+ 'update',
+ ['status' => $event->status()->toString()],
+ ['id' => $event->todoId()->toString()]
+ );
+ },
+ DeadlineWasAddedToTodo::class => function ($state, DeadlineWasAddedToTodo $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack(
+ 'update',
+ ['deadline' => $event->deadline()->toString()],
+ ['id' => $event->todoId()->toString()]
+ );
+ },
+ ReminderWasAddedToTodo::class => function ($state, ReminderWasAddedToTodo $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack(
+ 'update',
+ ['reminder' => $event->reminder()->toString()],
+ ['id' => $event->todoId()->toString()]
+ );
+ },
+ TodoWasMarkedAsExpired::class => function ($state, TodoWasMarkedAsExpired $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack(
+ 'update',
+ ['status' => $event->newStatus()->toString()],
+ ['id' => $event->todoId()->toString()]
+ );
+ },
+ TodoWasUnmarkedAsExpired::class => function ($state, TodoWasUnmarkedAsExpired $event) {
+ /** @var TodoReadModel $readModel */
+ $readModel = $this->readModel();
+ $readModel->stack(
+ 'update',
+ ['status' => $event->newStatus()->toString()],
+ ['id' => $event->todoId()->toString()]
+ );
+ },
+ ]);
+
+ return $projector;
+ }
+}
+```
+
+A lot of code, but really simple one. Its configuration is shorter.
+First we need to define a service definition for both:
+
+```yaml
+# app/config/services.yml
+services:
+ proophessor.projection.todo:
+ class: Prooph\ProophessorDo\Projection\Todo\TodoProjection
+
+ proophessor.projection.read_model.todo:
+ class: Prooph\ProophessorDo\Projection\Todo\TodoReadModel
+```
+
+Now we have two possibilities to configure the projections.
+
+## Tags
+
+We can add a Tag to the projection:
+
+```yaml
+# app/config/services.yml
+services:
+ proophessor.projection.todo:
+ class: Prooph\ProophessorDo\Projection\Todo\TodoProjection
+ tags:
+ - { name: prooph_event_store.projection, projection_name: todo_projection, projection_manager: acme_projection_manager, read_model: 'proophessor.projection.read_model.todo' }
+
+ proophessor.projection.read_model.todo:
+ class: Prooph\ProophessorDo\Projection\Todo\TodoReadModel
+```
+
+While the `projection_name` is freely selectable, the `projection_manager` must reference an existing Projection Manager
+(like the one we configured above).
+The `read_model` attribute is necessary only if the projection implements `Prooph\Bundle\EventStore\Projection\ReadModelProjection`.
+
+## Central
+
+If you do not like tags or want to configure your projections at a central place,
+you can do this directly at the `projection_manager`:
+
+```yaml
+# app/config/config.yml or (flex) config/packages/prooph_event_store.yaml
+prooph_event_store:
+ projection_managers:
+ acme_projection_manager:
+ event_store: 'prooph_event_store.pdo_mysql_event_store'
+ connection: 'pdo.connection'
+ projections:
+ todo_projection:
+ read_model: 'proophessor.projection.read_model.todo'
+ projection: 'proophessor.projection.todo'
+```
+
+As with the tag the `read_model` is necessary only if the projection implements `Prooph\Bundle\EventStore\Projection\ReadModelProjection`.
+
+Since both ways will produce the same result, it is up to you which of them you choose.
+
+## Running projections