Skip to content

Commit

Permalink
take snapshot by event name
Browse files Browse the repository at this point in the history
* If you have big events sometime it would be useful to create also a snapshot if this event occurs
  • Loading branch information
sandrokeil committed Oct 17, 2016
1 parent 61bd53d commit a8ffbff
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ php:

before_script:
- composer self-update
- composer --dev install
- composer --dev install --prefer-source

script:
- php ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml
Expand Down
9 changes: 9 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,14 @@
"ProophTest\\Snapshotter\\": "tests/",
"ProophTest\\EventStore\\": "vendor/prooph/event-store/tests/"
}
},
"scripts": {
"check": [
"@cs",
"@test"
],
"cs": "php-cs-fixer fix -v --diff --dry-run",
"cs-fix": "php-cs-fixer fix -v --diff",
"test": "phpunit"
}
}
6 changes: 4 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Configuration

To enable the Snapshot-Plugin simply attach it as plugin to the event-store.
It needs a list of aggregate root => aggregate repositories and a version step.
Version step 5 f.e. means take a snapshot every 5 versions.
It needs a list of aggregate root => aggregate repositories and a version step and/or a list of event names.
Version step 5 f.e. means take a snapshot every 5 versions. If event names provided, snaphosts also taken if one of
these events occurred.
If you use the provided factories from event-store and the snapshotter, you can simply do this by configuration:

return [
Expand All @@ -14,6 +15,7 @@ If you use the provided factories from event-store and the snapshotter, you can
],
'snapshotter' => [
'version_step' => 5,
'event_names' => ['awesomeEvent'],
'aggregate_repositories' => [
'My\Domain\AggregateRoot' => \My\Domain\AggregateRootRepository::class,
]
Expand Down
5 changes: 3 additions & 2 deletions src/Container/SnapshotPluginFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function __invoke(ContainerInterface $container)
$config = $container->get('config');
$config = $this->options($config);

return new SnapshotPlugin($container->get(CommandBus::class), $config['version_step']);
return new SnapshotPlugin($container->get(CommandBus::class), $config['version_step'], $config['event_names']);
}

/**
Expand All @@ -53,7 +53,8 @@ public function dimensions()
public function defaultOptions()
{
return [
'version_step' => 5
'version_step' => 5,
'event_names' => [],
];
}

Expand Down
28 changes: 25 additions & 3 deletions src/SnapshotPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,32 @@ final class SnapshotPlugin implements Plugin
*/
private $versionStep;

/**
* List of event names to take snapshot
*
* @var array
*/
private $eventNames;

/**
* Shortcut to determine if event names available
*
* @var bool
*/
private $hasEventNames;

/**
* @param CommandBus $commandBus
* @param int $versionStep
* @param array $eventNames
*/
public function __construct(CommandBus $commandBus, $versionStep)
public function __construct(CommandBus $commandBus, $versionStep, array $eventNames = [])
{
Assertion::min($versionStep, 1);
$this->commandBus = $commandBus;
$this->versionStep = $versionStep;
$this->eventNames = $eventNames;
$this->hasEventNames = !empty($eventNames);
}

/**
Expand All @@ -64,12 +81,17 @@ public function onEventStoreCommitPost(ActionEvent $actionEvent)

$snapshots = [];

/* @var $recordedEvent \Prooph\Common\Messaging\Message */
foreach ($recordedEvents as $recordedEvent) {
if ($recordedEvent->version() % $this->versionStep !== 0) {
$doSnapshot = $recordedEvent->version() % $this->versionStep === 0;

if (false === $doSnapshot && false === $this->hasEventNames) {
continue;
}
$metadata = $recordedEvent->metadata();
if (!isset($metadata['aggregate_type']) || !isset($metadata['aggregate_id'])) {
if (!isset($metadata['aggregate_type'], $metadata['aggregate_id'])
|| (false === $doSnapshot && !in_array($recordedEvent->messageName(), $this->eventNames, true))
) {
continue;
}
$snapshots[$metadata['aggregate_type']][] = $metadata['aggregate_id'];
Expand Down
19 changes: 16 additions & 3 deletions tests/Container/SnapshotPluginFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,25 @@ public function it_creates_snapshot_plugin()
'prooph' => [
'snapshotter' => [
'version_step' => 5,
]
]
'event_names' => ['awesomeEvent'],
],
],
]);
$container->get(CommandBus::class)->willReturn($commandBus->reveal());

$factory = new SnapshotPluginFactory();
$this->assertInstanceOf(SnapshotPlugin::class, $factory($container->reveal()));
$snapshotPlugin = $factory($container->reveal());

$this->assertInstanceOf(SnapshotPlugin::class, $snapshotPlugin);

$reflectionClass = new \ReflectionClass($snapshotPlugin);
$versionStepProperty = $reflectionClass->getProperty('versionStep');
$versionStepProperty->setAccessible(true);
$eventNamesProperty = $reflectionClass->getProperty('eventNames');
$eventNamesProperty->setAccessible(true);

$this->assertSame(5, $versionStepProperty->getValue($snapshotPlugin));
$this->assertArrayHasKey(0, $eventNamesProperty->getValue($snapshotPlugin));
$this->assertSame('awesomeEvent', $eventNamesProperty->getValue($snapshotPlugin)[0]);
}
}
112 changes: 84 additions & 28 deletions tests/SnapshotPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,36 @@
final class SnapshotPluginTest extends TestCase
{
/**
* @test
* @var EventStore
*/
public function it_publishes_take_snapshot_commands_for_all_known_aggregates()
private $eventStore;

/**
* @var AggregateRepository
*/
private $repository;

private $result;

public function setUp()
{
$inMemoryAdapter = new InMemoryAdapter();
$eventEmitter = new ProophActionEventEmitter();

$eventStore = new EventStore($inMemoryAdapter, $eventEmitter);
$this->eventStore = new EventStore($inMemoryAdapter, $eventEmitter);

$repository = new AggregateRepository(
$eventStore,
$this->repository = new AggregateRepository(
$this->eventStore,
AggregateType::fromAggregateRootClass('ProophTest\EventStore\Mock\User'),
new ConfigurableAggregateTranslator()
);

$result = [];
$this->result = [];
$self = $this;

$router = new CommandRouter();
$router->route(TakeSnapshot::class)->to(function (TakeSnapshot $command) use (&$result) {
$result[] = [
$router->route(TakeSnapshot::class)->to(function (TakeSnapshot $command) use ($self) {
$self->result[] = [
'aggregate_type' => $command->aggregateType(),
'aggregate_id' => $command->aggregateId()
];
Expand All @@ -63,33 +73,39 @@ public function it_publishes_take_snapshot_commands_for_all_known_aggregates()
$commandBus->utilize($router);

$plugin = new SnapshotPlugin($commandBus, 2);
$plugin->setUp($eventStore);
$plugin->setUp($this->eventStore);

$eventStore->beginTransaction();
$this->eventStore->beginTransaction();

$eventStore->create(new Stream(new StreamName('event_stream'), new \ArrayIterator()));
$this->eventStore->create(new Stream(new StreamName('event_stream'), new \ArrayIterator()));

$eventStore->commit();
$this->eventStore->commit();
}

$eventStore->beginTransaction();
/**
* @test
*/
public function it_publishes_take_snapshot_commands_for_all_known_aggregates()
{
$this->eventStore->beginTransaction();

$user = User::create('Alex', '[email protected]');

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

$eventStore->commit();
$this->eventStore->commit();

$eventStore->beginTransaction();
$this->eventStore->beginTransaction();

$user = $repository->getAggregateRoot($user->getId()->toString());
$user = $this->repository->getAggregateRoot($user->getId()->toString());

$user->changeName('John');
$user->changeName('Jane');
$user->changeName('Jim');

$eventStore->commit();
$this->eventStore->commit();

$eventStore->beginTransaction();
$this->eventStore->beginTransaction();

$eventWithoutMetadata1 = UsernameChanged::with(
['new_name' => 'John Doe'],
Expand All @@ -101,19 +117,59 @@ public function it_publishes_take_snapshot_commands_for_all_known_aggregates()
6
);

$eventStore->appendTo(new StreamName('event_stream'), new \ArrayIterator([
$this->eventStore->appendTo(new StreamName('event_stream'), new \ArrayIterator([
$eventWithoutMetadata1,
$eventWithoutMetadata2
]));

$eventStore->commit();
$this->eventStore->commit();

$this->assertCount(2, $this->result);
$this->assertArrayHasKey('aggregate_type', $this->result[0]);
$this->assertArrayHasKey('aggregate_id', $this->result[0]);
$this->assertArrayHasKey('aggregate_type', $this->result[1]);
$this->assertArrayHasKey('aggregate_id', $this->result[1]);
$this->assertEquals(User::class, $this->result[0]['aggregate_type']);
$this->assertEquals(User::class, $this->result[1]['aggregate_type']);
}

/**
* @test
*/
public function it_publishes_take_snapshot_commands_by_event_name()
{
$this->eventStore->beginTransaction();

$user = User::create('Alex', '[email protected]');

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

$this->eventStore->commit();

$this->eventStore->beginTransaction();

$user = $this->repository->getAggregateRoot($user->getId()->toString());

$user->changeName('Jim');

$this->eventStore->commit();

$this->eventStore->beginTransaction();

$eventWithoutMetadata1 = UsernameChanged::with(
['new_name' => 'John Doe'],
5
);

$this->eventStore->appendTo(new StreamName('event_stream'), new \ArrayIterator([
$eventWithoutMetadata1,
]));

$this->eventStore->commit();

$this->assertCount(2, $result);
$this->assertArrayHasKey('aggregate_type', $result[0]);
$this->assertArrayHasKey('aggregate_id', $result[0]);
$this->assertArrayHasKey('aggregate_type', $result[1]);
$this->assertArrayHasKey('aggregate_id', $result[1]);
$this->assertEquals(User::class, $result[0]['aggregate_type']);
$this->assertEquals(User::class, $result[1]['aggregate_type']);
$this->assertCount(1, $this->result);
$this->assertArrayHasKey('aggregate_type', $this->result[0]);
$this->assertArrayHasKey('aggregate_id', $this->result[0]);
$this->assertEquals(User::class, $this->result[0]['aggregate_type']);
}
}

0 comments on commit a8ffbff

Please sign in to comment.