Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option to disable identity map #77

Merged
merged 1 commit into from
May 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/interop_factories.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ You can add your custom event store too (default is `EventStore::class`):
]
```

You can also disable the identity map
```php
[
'prooph' => [
'event_sourcing' => [
'aggregate_repository' => [
'user_repository' => [
'repository_class' => MyUserRepository::class,
'event_store' => MyCustomEventStore::class, // <-- Custom event store service id
'aggregate_type' => MyUser::class,
'aggregate_translator' => 'user_translator',
'disable_identity_map' => true,
],
],
],
],
]
```

Last but not least you can enable the so called "One-Stream-Per-Aggregate-Mode":
```php
[
Expand Down
8 changes: 5 additions & 3 deletions docs/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ processes dealing with the same aggregate would run into concurrency issues very
The test case has some more tests including snapshot usage and working with different stream names / strategies.
Just browse through the test methods for details.

You can also disable the identity map by passing that option to the constructor (provided interop-factory can do this for you as well).

## Aggregate Type Mapping

It's possible to map an aggregate type `user` to an aggregate root class like `My\Model\User`. To do that, add the
Expand Down Expand Up @@ -186,9 +188,9 @@ Example configuration:
## Loading of thousands aggregates

If you need to load thousands of aggregates for reading only, your memory can be exhausted, because the
`AggregateRepository` uses an identity map. So every loaded aggregate is stored there, unless a commit is executed. If
you have a read only process, you should consider to clear the identity map at some time. This can be done by calling
`clearIdentityMap()`.
`AggregateRepository` uses an identity map (if it's not disabled). So every loaded aggregate is stored there,
unless a commit is executed. If you have a read only process, you should consider to clear the identity map
at some time. This can be done by calling `clearIdentityMap()`.

```php
$thousandsOfAggregateIds = [ // lots of ids here ];
Expand Down
21 changes: 15 additions & 6 deletions src/Aggregate/AggregateRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,27 @@ class AggregateRepository
*/
protected $oneStreamPerAggregate;

/**
* @var bool
*/
protected $disableIdentityMap;

public function __construct(
EventStore $eventStore,
AggregateType $aggregateType,
AggregateTranslator $aggregateTranslator,
SnapshotStore $snapshotStore = null,
StreamName $streamName = null,
bool $oneStreamPerAggregate = false
bool $oneStreamPerAggregate = false,
bool $disableIdentityMap = false
) {
$this->eventStore = $eventStore;
$this->aggregateType = $aggregateType;
$this->aggregateTranslator = $aggregateTranslator;
$this->snapshotStore = $snapshotStore;
$this->streamName = $streamName;
$this->oneStreamPerAggregate = $oneStreamPerAggregate;
$this->disableIdentityMap = $disableIdentityMap;
}

/**
Expand Down Expand Up @@ -114,7 +121,7 @@ public function saveAggregateRoot($eventSourcedAggregateRoot): void
$this->eventStore->appendTo($streamName, new ArrayIterator($enrichedEvents));
}

if (isset($this->identityMap[$aggregateId])) {
if (! $this->disableIdentityMap && isset($this->identityMap[$aggregateId])) {
unset($this->identityMap[$aggregateId]);
}
}
Expand All @@ -126,14 +133,14 @@ public function saveAggregateRoot($eventSourcedAggregateRoot): void
*/
public function getAggregateRoot(string $aggregateId)
{
if (isset($this->identityMap[$aggregateId])) {
if (! $this->disableIdentityMap && isset($this->identityMap[$aggregateId])) {
return $this->identityMap[$aggregateId];
}

if ($this->snapshotStore) {
$eventSourcedAggregateRoot = $this->loadFromSnapshotStore($aggregateId);

if ($eventSourcedAggregateRoot) {
if ($eventSourcedAggregateRoot && ! $this->disableIdentityMap) {
//Cache aggregate root in the identity map
$this->identityMap[$aggregateId] = $eventSourcedAggregateRoot;
}
Expand Down Expand Up @@ -178,8 +185,10 @@ public function getAggregateRoot(string $aggregateId)
$streamEvents
);

//Cache aggregate root in the identity map but without pending events
$this->identityMap[$aggregateId] = $eventSourcedAggregateRoot;
if (! $this->disableIdentityMap) {
//Cache aggregate root in the identity map but without pending events
$this->identityMap[$aggregateId] = $eventSourcedAggregateRoot;
}

return $eventSourcedAggregateRoot;
}
Expand Down
18 changes: 13 additions & 5 deletions src/Container/Aggregate/AggregateRepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,18 @@ public function __invoke(ContainerInterface $container): AggregateRepository
$repositoryClass = $config['repository_class'];

if (! class_exists($repositoryClass)) {
throw ConfigurationException::configurationError(sprintf('Repository class %s cannot be found', $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));
throw ConfigurationException::configurationError(sprintf(
'Repository class %s must be a sub class of %s',
$repositoryClass,
AggregateRepository::class
));
}

$eventStore = $container->get($config['event_store']);
Expand All @@ -96,15 +103,14 @@ public function __invoke(ContainerInterface $container): AggregateRepository

$streamName = isset($config['stream_name']) ? new StreamName($config['stream_name']) : null;

$oneStreamPerAggregate = (bool) ($config['one_stream_per_aggregate'] ?? false);

return new $repositoryClass(
$eventStore,
$aggregateType,
$aggregateTranslator,
$snapshotStore,
$streamName,
$oneStreamPerAggregate
$config['one_stream_per_aggregate'],
$config['disable_identity_map']
);
}

Expand All @@ -131,6 +137,8 @@ public function defaultOptions(): iterable
{
return [
'event_store' => EventStore::class,
'one_stream_per_aggregate' => false,
'disable_identity_map' => false,
];
}
}
30 changes: 30 additions & 0 deletions tests/Aggregate/AggregateRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,36 @@ public function it_adds_a_new_aggregate(): void
$this->assertEquals('John Doe', $fetchedUser->name());
}

/**
* @test
*/
public function it_ignores_identity_map_if_disabled(): void
{
$this->repository = new AggregateRepository(
$this->eventStore,
AggregateType::fromAggregateRootClass(User::class),
new AggregateTranslator(),
null,
null,
false,
true
);

$user = User::nameNew('John Doe');

$this->repository->saveAggregateRoot($user);

$fetchedUser = $this->repository->getAggregateRoot(
$user->id()
);

$fetchedUser2 = $this->repository->getAggregateRoot(
$user->id()
);

$this->assertNotSame($fetchedUser, $fetchedUser2);
}

/**
* @test
*/
Expand Down