Skip to content

Commit 568da9f

Browse files
committed
Add QueryBus integration test which was missing, also add "full cycle" bench which dispatches a command and then query the name.
Fix a deprecation notice regarding `SplObjectStorage`
1 parent 440a663 commit 568da9f

10 files changed

Lines changed: 333 additions & 7 deletions

File tree

src/Subscription/Engine/SubscriptionManager.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,48 +77,48 @@ public function find(SubscriptionCriteria $criteria): array
7777
public function add(Subscription ...$subscriptions): void
7878
{
7979
foreach ($subscriptions as $sub) {
80-
$this->forAdd->attach($sub);
80+
$this->forAdd->offsetSet($sub);
8181
}
8282
}
8383

8484
public function update(Subscription ...$subscriptions): void
8585
{
8686
foreach ($subscriptions as $sub) {
87-
$this->forUpdate->attach($sub);
87+
$this->forUpdate->offsetSet($sub);
8888
}
8989
}
9090

9191
public function remove(Subscription ...$subscriptions): void
9292
{
9393
foreach ($subscriptions as $sub) {
94-
$this->forRemove->attach($sub);
94+
$this->forRemove->offsetSet($sub);
9595
}
9696
}
9797

9898
public function flush(): void
9999
{
100100
foreach ($this->forAdd as $subscription) {
101-
if ($this->forRemove->contains($subscription)) {
101+
if ($this->forRemove->offsetExists($subscription)) {
102102
continue;
103103
}
104104

105105
$this->subscriptionStore->add($subscription);
106106
}
107107

108108
foreach ($this->forUpdate as $subscription) {
109-
if ($this->forAdd->contains($subscription)) {
109+
if ($this->forAdd->offsetExists($subscription)) {
110110
continue;
111111
}
112112

113-
if ($this->forRemove->contains($subscription)) {
113+
if ($this->forRemove->offsetExists($subscription)) {
114114
continue;
115115
}
116116

117117
$this->subscriptionStore->update($subscription);
118118
}
119119

120120
foreach ($this->forRemove as $subscription) {
121-
if ($this->forAdd->contains($subscription)) {
121+
if ($this->forAdd->offsetExists($subscription)) {
122122
continue;
123123
}
124124

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Command;
6+
7+
use Patchlevel\EventSourcing\Attribute\Id;
8+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId;
9+
10+
final class ChangeProfileName
11+
{
12+
public function __construct(
13+
#[Id]
14+
public readonly ProfileId $id,
15+
public readonly string $name,
16+
) {
17+
}
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Command;
6+
7+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId;
8+
9+
final class CreateProfile
10+
{
11+
public function __construct(
12+
public readonly ProfileId $id,
13+
public readonly string $name,
14+
) {
15+
}
16+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation;
6+
7+
use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot;
8+
use Patchlevel\EventSourcing\Attribute\Aggregate;
9+
use Patchlevel\EventSourcing\Attribute\Apply;
10+
use Patchlevel\EventSourcing\Attribute\Handle;
11+
use Patchlevel\EventSourcing\Attribute\Id;
12+
use Patchlevel\EventSourcing\Attribute\Inject;
13+
use Patchlevel\EventSourcing\Attribute\Snapshot;
14+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Command\ChangeProfileName;
15+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Command\CreateProfile;
16+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged;
17+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated;
18+
use Psr\Clock\ClockInterface;
19+
20+
#[Aggregate('profile_with_commands')]
21+
#[Snapshot('default', 100)]
22+
final class ProfileWithCommands extends BasicAggregateRoot
23+
{
24+
#[Id]
25+
private ProfileId $id;
26+
private string $name;
27+
28+
#[Handle]
29+
public static function create(
30+
CreateProfile $command,
31+
ClockInterface $clock,
32+
#[Inject('env')]
33+
string $env,
34+
): self {
35+
$self = new self();
36+
$self->recordThat(new ProfileCreated($command->id, $command->name, null));
37+
38+
return $self;
39+
}
40+
41+
#[Handle]
42+
public function changeName(
43+
ChangeProfileName $command,
44+
ClockInterface $clock,
45+
#[Inject('env')]
46+
string $env,
47+
): void {
48+
$this->recordThat(new NameChanged($command->name));
49+
}
50+
51+
#[Apply]
52+
protected function applyProfileCreated(ProfileCreated $event): void
53+
{
54+
$this->id = $event->profileId;
55+
$this->name = $event->name;
56+
}
57+
58+
#[Apply]
59+
protected function applyNameChanged(NameChanged $event): void
60+
{
61+
$this->name = $event->name;
62+
}
63+
64+
public function name(): string
65+
{
66+
return $this->name;
67+
}
68+
}

tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Projection;
66

77
use Doctrine\DBAL\Connection;
8+
use Patchlevel\EventSourcing\Attribute\Answer;
89
use Patchlevel\EventSourcing\Attribute\Projector;
910
use Patchlevel\EventSourcing\Attribute\Setup;
1011
use Patchlevel\EventSourcing\Attribute\Subscribe;
@@ -13,6 +14,7 @@
1314
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged;
1415
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated;
1516
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId;
17+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Query\QueryProfileName;
1618

1719
#[Projector('profile')]
1820
final class ProfileProjector
@@ -58,6 +60,15 @@ public function onNameChanged(NameChanged $nameChanged, ProfileId $profileId): v
5860
);
5961
}
6062

63+
#[Answer]
64+
public function getProfileName(QueryProfileName $queryProfileName): string
65+
{
66+
return $this->connection->fetchAssociative(
67+
"SELECT name FROM {$this->table()} WHERE id = :id",
68+
['id' => $queryProfileName->id->toString()],
69+
)['name'];
70+
}
71+
6172
public function table(): string
6273
{
6374
return 'projection_' . $this->subscriberId();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Query;
6+
7+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId;
8+
9+
final readonly class QueryProfileName
10+
{
11+
public function __construct(public ProfileId $id){}
12+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Benchmark;
6+
7+
use Patchlevel\EventSourcing\Clock\SystemClock;
8+
use Patchlevel\EventSourcing\CommandBus\CommandBus;
9+
use Patchlevel\EventSourcing\CommandBus\ServiceLocator;
10+
use Patchlevel\EventSourcing\CommandBus\SyncCommandBus;
11+
use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;
12+
use Patchlevel\EventSourcing\QueryBus\QueryBus;
13+
use Patchlevel\EventSourcing\QueryBus\ServiceHandlerProvider;
14+
use Patchlevel\EventSourcing\QueryBus\SyncQueryBus;
15+
use Patchlevel\EventSourcing\Repository\DefaultRepositoryManager;
16+
use Patchlevel\EventSourcing\Schema\DoctrineSchemaDirector;
17+
use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
18+
use Patchlevel\EventSourcing\Snapshot\Adapter\InMemorySnapshotAdapter;
19+
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
20+
use Patchlevel\EventSourcing\Store\StreamDoctrineDbalStore;
21+
use Patchlevel\EventSourcing\Subscription\Engine\DefaultSubscriptionEngine;
22+
use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngine;
23+
use Patchlevel\EventSourcing\Subscription\Repository\RunSubscriptionEngineRepositoryManager;
24+
use Patchlevel\EventSourcing\Subscription\Store\InMemorySubscriptionStore;
25+
use Patchlevel\EventSourcing\Subscription\Subscriber\MetadataSubscriberAccessorRepository;
26+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Command\ChangeProfileName;
27+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Command\CreateProfile;
28+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Processor\SendEmailProcessor;
29+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId;
30+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileWithCommands;
31+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Projection\ProfileProjector;
32+
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Query\QueryProfileName;
33+
use Patchlevel\EventSourcing\Tests\DbalManager;
34+
use PhpBench\Attributes as Bench;
35+
use Psr\Clock\ClockInterface;
36+
37+
#[Bench\BeforeMethods('setUp')]
38+
final class CommandToQueryBench
39+
{
40+
private CommandBus $commandBus;
41+
private QueryBus $queryBus;
42+
43+
private ProfileId $updateId;
44+
45+
public function setUp(): void
46+
{
47+
$connection = DbalManager::createConnection();
48+
$store = new StreamDoctrineDbalStore(
49+
$connection,
50+
DefaultEventSerializer::createFromPaths([__DIR__ . '/BasicImplementation/Events']),
51+
);
52+
53+
$aggregateRootRegistry = new AggregateRootRegistry(['profile_with_commands' => ProfileWithCommands::class]);
54+
55+
$manager = new DefaultRepositoryManager(
56+
$aggregateRootRegistry,
57+
$store,
58+
null,
59+
new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]),
60+
);
61+
62+
$projectionConnection = DbalManager::createConnection();
63+
$profileProjection = new ProfileProjector($projectionConnection);
64+
65+
$engine = new DefaultSubscriptionEngine(
66+
$store,
67+
new InMemorySubscriptionStore(),
68+
new MetadataSubscriberAccessorRepository([
69+
$profileProjection,
70+
new SendEmailProcessor(),
71+
]),
72+
);
73+
74+
$manager = new RunSubscriptionEngineRepositoryManager($manager, $engine);
75+
76+
$this->commandBus = SyncCommandBus::createForAggregateHandlers(
77+
$aggregateRootRegistry,
78+
$manager,
79+
new ServiceLocator([
80+
ClockInterface::class => new SystemClock(),
81+
'env' => 'test',
82+
]),
83+
);
84+
85+
$this->queryBus = new SyncQueryBus(new ServiceHandlerProvider([$profileProjection]));
86+
87+
$schemaDirector = new DoctrineSchemaDirector($connection, $store);
88+
89+
$schemaDirector->create();
90+
$engine->setup(skipBooting: true);
91+
92+
$this->updateId = ProfileId::generate();
93+
$this->commandBus->dispatch(new CreateProfile($this->updateId, 'James'));
94+
}
95+
96+
#[Bench\Revs(10)]
97+
public function benchCreate(): void
98+
{
99+
$id = ProfileId::generate();
100+
$this->commandBus->dispatch(new CreateProfile($id, 'James'));
101+
$this->queryBus->dispatch(new QueryProfileName($id));
102+
}
103+
104+
#[Bench\Revs(10)]
105+
public function benchUpdate(): void
106+
{
107+
$this->commandBus->dispatch(new ChangeProfileName($this->updateId, 'James Doe'));
108+
$this->queryBus->dispatch(new QueryProfileName($this->updateId));
109+
}
110+
}

0 commit comments

Comments
 (0)