Skip to content

Commit

Permalink
Merge pull request #131 from codeliner/feature/ar_factory
Browse files Browse the repository at this point in the history
Add AbstractAggregateRootFactory
  • Loading branch information
prolic committed Oct 21, 2015
2 parents 4854029 + 307d20e commit 46270db
Show file tree
Hide file tree
Showing 9 changed files with 468 additions and 44 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ For a short overview please see the annotated [quickstart.php](examples/quicksta
- [Apply Events Late](docs/apply_events_late.md)
- [Replay History](docs/replay_history.md)
- [Upcasting](docs/upcasting.md)
- [Framework Integration](docs/interop_factories.md)

# Support

Expand Down
46 changes: 2 additions & 44 deletions docs/event_store.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,48 +61,6 @@ The event-driven system opens the door for customizations. Here are some ideas w
- Implement your own Unit of Work and synchronizes it with the `transaction`, `commit.pre/post` and `rollback` events
- ...

## Container-Driven Creation
## Factory-Driven Creation

If you are familiar with the factory pattern and you use an implementation of [interop-container](https://github.com/container-interop/container-interop)
in your project you may want to have a look at the factories shipped with prooph/event-store.
You can find them in the [Container](../src/Container) folder.

### Requirements

1. Your Inversion of Control container must implement the [interop-container interface](https://github.com/container-interop/container-interop).
2. The application configuration should be registered with the service id `config` in the container.

*Note: Don't worry, if your environment doesn't provide the requirements. You can
always bootstrap the event store by hand. Just look at the factories for inspiration in this case.*

If the requirements are met you just need to add a new section in your application config ...

```php
[
'prooph' => [
'event_store' => [
'adapter' => [
'type' => 'adapter_service_id', //The factory will use this id to get the adapter from the container
//The options key is reserved for adapter factories
'options' => []
],
'event_emitter' => 'emitter_service_id' //The factory will use this id to get the event emitter from the container
'plugins' => [
//And again the factory will use each service id to get the plugin from the container
//Plugin::setUp($eventStore) is then invoked by the factory so your plugins get attached automatically
//Awesome, isn't it?
'plugin_1_service_id',
'plugin_2_service_id',
//...
]
]
],
//... other application config here
]
```

... and register the factory in your IoC container. We recommend using the service id `Prooph\EventStore\EventStore (EventStore::class)` for the event store
because other factories like the [stream factories](../src/Container/Stream) try to locate the event store
by using this service id.

*Note: The available event store adapters also ship with factories. Please refer to the adapter packages for details.*
See [Interop Factories](interop_factories.md)
97 changes: 97 additions & 0 deletions docs/interop_factories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Interop Factories

Instead of providing a module, a bundle, a bridge or similar framework integration prooph/event-store ships with `interop factories`.

## Factory-Driven Creation

The concept behind these [factories](../src/Container) is simple but powerful. It allows us to provide you with bootstrapping logic for the event store and related components
without the need to rely on a specific framework. However, the factories have three requirements.

### Requirements

1. Your Inversion of Control container must implement the [interop-container interface](https://github.com/container-interop/container-interop).
2. [interop-config](https://github.com/sandrokeil/interop-config) must be installed
3. The application configuration should be registered with the service id `config` in the container.

*Note: Don't worry, if your environment doesn't provide the requirements. You can
always bootstrap the components by hand. Just look at the factories for inspiration in this case.*

### Event Store Factory

If the requirements are met you just need to add a new section in your application config ...

```php
[
'prooph' => [
'event_store' => [
'adapter' => [
'type' => 'adapter_service_id', //The factory will use this id to get the adapter from the container
//The options key is reserved for adapter factories
'options' => []
],
'event_emitter' => 'emitter_service_id' //The factory will use this id to get the event emitter from the container
'plugins' => [
//And again the factory will use each service id to get the plugin from the container
//Plugin::setUp($eventStore) is then invoked by the factory so your plugins get attached automatically
//Awesome, isn't it?
'plugin_1_service_id',
'plugin_2_service_id',
//...
]
]
],
//... other application config here
]
```

... and register the [EventStoreFactory](../src/Container/EventStoreFactory.php) in your IoC container. We recommend using the service id `Prooph\EventStore\EventStore (EventStore::class)` for the event store
because other factories like the [stream factories](../src/Container/Stream) try to locate the event store
by using this service id.

*Note: The available event store adapters also ship with factories. Please refer to the adapter packages for details.*

### AbstractAggregateRepositoryFactory

To ease set up of repositories for your aggregate roots prooph/event-store also ships with a [AbstractAggregateRepositoryFactory](../src/Aggregate/AbstractAggregateRepositoryFactory.php).
It is an abstract class implementing the `container-interop RequiresContainerId` interface. The `containerId` method
itself is not implemented in the abstract class. You have to extend it and provide the container id because each
aggregate repository needs a slightly different configuration and therefor needs its own config key.

*Note: You can have a look at the [RepositoryMock](../tests/Mock/RepositoryMock.php). It sounds more complex than it is.*

Let's say we have a repository factory for a User aggregate root. We use `user_repository` as container id and add this
configuration to our application configuration:

```php
[
'prooph' => [
'event_store' => [
'user_repository' => [ //<-- here the container id is referenced
'repository_class' => MyUserRepository::class, //<-- FQCN of the repository responsible for the aggregate root
'aggregate_type' => MyUser::class, //<-- The aggregate root FQCN the repository is responsible for
'aggregate_translator' => 'user_translator', //<-- The aggregate translator must be available as service in the container
]
]
]
]
```

If you also want to configure a custom stream strategy or want to make use of a snapshot adapter then you need to make
them available as services in the container and use the configuration to let the factory inject them in the repository.

```php
[
'prooph' => [
'event_store' => [
'user_repository' => [
'repository_class' => MyUserRepository::class,
'aggregate_type' => MyUser::class,
'aggregate_translator' => 'user_translator',
'stream_strategy' => 'user_stream' // <-- Custom stream strategy service id
'snapshot_store' => 'awesome_snapshot_store' // <-- SnapshotStore service id
]
]
]
]
```

5 changes: 5 additions & 0 deletions docs/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,8 @@ a repository method. Only `$this->eventStore->commit();` is called. But as you c
is changed and the appropriate domain event was added to the `event_stream`. This happens becasue the repository manages an identity map
internally. Each aggregate root loaded via `AggregateRepository::getAggregateRoot` is added to the identity map and
new events recorded by such an agggregate root are added automatically to the event stream on `EventStore::commit`.

## Factory-Driven Creation

See [Interop Factories](interop_factories.md)

89 changes: 89 additions & 0 deletions src/Container/Aggregate/AbstractAggregateRepositoryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the prooph/event-store.
* (c) 2014-2015 prooph software GmbH <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Date: 10/21/15 - 6:10 PM
*/
namespace Prooph\EventStore\Container\Aggregate;

use Interop\Config\ConfigurationTrait;
use Interop\Config\RequiresContainerId;
use Interop\Config\RequiresMandatoryOptions;
use Interop\Container\ContainerInterface;
use Prooph\EventStore\Aggregate\AggregateRepository;
use Prooph\EventStore\Aggregate\AggregateType;
use Prooph\EventStore\EventStore;
use Prooph\EventStore\Exception\ConfigurationException;

/**
* Class AbstractAggregateRepositoryFactory
*
* @package Prooph\EventStore\Container
*/
abstract class AbstractAggregateRepositoryFactory implements RequiresContainerId, RequiresMandatoryOptions
{
use ConfigurationTrait;

/**
* @param ContainerInterface $container
* @throws ConfigurationException
* @return AggregateRepository
*/
public function __invoke(ContainerInterface $container)
{
$config = $container->get('config');
$config = $this->options($config);

$repositoryClass = $config['repository_class'];

if (! class_exists($repositoryClass)) {
throw ConfigurationException::configurationError(sprintf('Repository class %s cannot be found', $repositoryClass));
}

if (! is_subclass_of($repositoryClass, AggregateRepository::class)) {
throw ConfigurationException::configurationError(sprintf('Repository class %s must be a sub class of %s', $repositoryClass, AggregateRepository::class));
}

$eventStore = $container->get(EventStore::class);
$aggregateType = AggregateType::fromAggregateRootClass($config['aggregate_type']);
$aggregateTranslator = $container->get($config['aggregate_translator']);

$streamStrategy = isset($config['stream_strategy'])? $container->get($config['stream_strategy']) : null;

$snapshotStore = isset($config['snapshot_store'])? $container->get($config['snapshot_store']) : null;

return new $repositoryClass($eventStore, $aggregateType, $aggregateTranslator, $streamStrategy, $snapshotStore);
}

/**
* @inheritdoc
*/
public function vendorName()
{
return 'prooph';
}

/**
* @inheritdoc
*/
public function packageName()
{
return 'event_store';
}

/**
* @inheritdoc
*/
public function mandatoryOptions()
{
return [
'repository_class',
'aggregate_type',
'aggregate_translator',
];
}
}
Loading

0 comments on commit 46270db

Please sign in to comment.