Skip to content

Commit

Permalink
improve aggregate id argument resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidBadura committed Apr 12, 2024
1 parent 07c4a52 commit b681fcb
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 51 deletions.
22 changes: 9 additions & 13 deletions docs/pages/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,11 @@ Each projector is then responsible for a specific projection.

```php
use Doctrine\DBAL\Connection;
use Patchlevel\EventSourcing\Aggregate\AggregateHeader;
use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Attribute\Projector;
use Patchlevel\EventSourcing\Attribute\Setup;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Teardown;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;

#[Projector('hotel')]
Expand All @@ -182,35 +181,33 @@ final class HotelProjector
}

#[Subscribe(HotelCreated::class)]
public function handleHotelCreated(Message $message): void
public function handleHotelCreated(HotelCreated $event, Uuid $aggregateId): void
{
$event = $message->event();

$this->db->insert(
$this->table(),
[
'id' => $message->header(AggregateHeader::class)->aggregateId,
'id' => $aggregateId->toString(),
'name' => $event->hotelName,
'guests' => 0,
],
);
}

#[Subscribe(GuestIsCheckedIn::class)]
public function handleGuestIsCheckedIn(Message $message): void
public function handleGuestIsCheckedIn(Uuid $aggregateId): void
{
$this->db->executeStatement(
"UPDATE {$this->table()} SET guests = guests + 1 WHERE id = ?;",
[$message->header(AggregateHeader::class)->aggregateId],
[$aggregateId->toString()],
);
}

#[Subscribe(GuestIsCheckedOut::class)]
public function handleGuestIsCheckedOut(Message $message): void
public function handleGuestIsCheckedOut(Uuid $aggregateId): void
{
$this->db->executeStatement(
"UPDATE {$this->table()} SET guests = guests - 1 WHERE id = ?;",
[$message->header(AggregateHeader::class)->aggregateId],
[$aggregateId->toString()],
);
}

Expand Down Expand Up @@ -243,7 +240,6 @@ In our example we also want to email the head office as soon as a guest is check
```php
use Patchlevel\EventSourcing\Attribute\Processor;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Message\Message;

#[Processor('admin_emails')]
final class SendCheckInEmailProcessor
Expand All @@ -254,12 +250,12 @@ final class SendCheckInEmailProcessor
}

#[Subscribe(GuestIsCheckedIn::class)]
public function onGuestIsCheckedIn(Message $message): void
public function onGuestIsCheckedIn(GuestIsCheckedIn $event): void
{
$this->mailer->send(
'[email protected]',
'Guest is checked in',
sprintf('A new guest named "%s" is checked in', $message->event()->guestName),
sprintf('A new guest named "%s" is checked in', $event->guestName),
);
}
}
Expand Down
91 changes: 91 additions & 0 deletions docs/pages/subscription.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,97 @@ final class DoStuffSubscriber
If you are using psalm then you can install the event sourcing [plugin](https://github.com/patchlevel/event-sourcing-psalm-plugin)
to make the event method return the correct type.

#### Argument Resolver

The library analyses the method signature and tries to resolve the arguments.
The order of the arguments doesn't matter, you can use multiple arguments and mix them.

##### Message Resolver

The message resolver resolves the `Message` object.
It looks for a parameter with the type `Message`.

```php
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Subscription\RunMode;

#[Subscriber('do_stuff', RunMode::Once)]
final class DoStuffSubscriber
{
#[Subscribe(ProfileCreated::class)]
public function onProfileCreated(Message $message): void
{
// do something
}
}
```
##### Event Resolver

The event resolver resolves the event object.
It looks for a parameter with the type of the event.

```php
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Subscription\RunMode;

#[Subscriber('do_stuff', RunMode::Once)]
final class DoStuffSubscriber
{
#[Subscribe(ProfileCreated::class)]
public function onProfileCreated(ProfileCreated $profileCreated): void
{
// do something
}
}
```
##### Aggregate Id Resolver

The aggregate id resolver resolves the aggregate id.
It looks for a parameter with the instance of the `AggregateRootId`.

```php
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Subscription\RunMode;

#[Subscriber('do_stuff', RunMode::Once)]
final class DoStuffSubscriber
{
#[Subscribe(ProfileCreated::class)]
public function onProfileCreated(ProfileId $profileId): void
{
// do something
}
}
```
!!! warning

The resolver argument doesn't know if you're using the correct aggregate id class and doesn't check it.
It gets the Aggregate ID as a string, takes the class and instantiates it with the method `fromString`.

##### Recorded On Resolver

The recorded on resolver resolves the recorded on date.
It looks for a parameter with the instance of the `DateTimeImmutable`.

```php
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Subscription\RunMode;

#[Subscriber('do_stuff', RunMode::Once)]
final class DoStuffSubscriber
{
#[Subscribe(ProfileCreated::class)]
public function onProfileCreated(DateTimeImmutable $recordedOn): void
{
// do something
}
}
```
### Setup and Teardown

Subscribers can have one `setup` and `teardown` method that is executed when the subscription is created or deleted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@
namespace Patchlevel\EventSourcing\Subscription\Subscriber\ArgumentResolver;

use Patchlevel\EventSourcing\Aggregate\AggregateHeader;
use Patchlevel\EventSourcing\Aggregate\AggregateRootId;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Metadata\Subscriber\ArgumentMetadata;

use function in_array;
use function class_exists;
use function is_a;

final class AggregateIdArgumentResolver implements ArgumentResolver
{
public function resolve(ArgumentMetadata $argument, Message $message): string
public function resolve(ArgumentMetadata $argument, Message $message): AggregateRootId
{
return $message->header(AggregateHeader::class)->aggregateId;
/** @var class-string<AggregateRootId> $class */
$class = $argument->type;

$id = $message->header(AggregateHeader::class)->aggregateId;

return $class::fromString($id);
}

public function support(ArgumentMetadata $argument, string $eventClass): bool
{
return $argument->type === 'string' && in_array($argument->name, ['aggregateId', 'aggregateRootId']);
return class_exists($argument->type) && is_a($argument->type, AggregateRootId::class, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\NameChanged;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Events\ProfileCreated;
use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId;

#[Projector('profile')]
final class ProfileProjector
Expand Down Expand Up @@ -48,12 +49,12 @@ public function onProfileCreated(ProfileCreated $profileCreated): void
}

#[Subscribe(NameChanged::class)]
public function onNameChanged(NameChanged $nameChanged, string $aggregateRootId): void
public function onNameChanged(NameChanged $nameChanged, ProfileId $profileId): void
{
$this->connection->update(
$this->table(),
['name' => $nameChanged->name],
['id' => $aggregateRootId],
['id' => $profileId->toString()],
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Attribute\Teardown;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Subscription\RunMode;
use RuntimeException;

Expand Down Expand Up @@ -36,7 +35,7 @@ public function teardown(): void
}

#[Subscribe('*')]
public function subscribe(Message $message): void
public function subscribe(): void
{
if ($this->subscribeError) {
throw new RuntimeException('subscribe error');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
use Patchlevel\EventSourcing\Attribute\Setup;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Teardown;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
use Patchlevel\EventSourcing\Tests\Integration\Subscription\Events\ProfileCreated;

use function assert;

#[Projector('profile_2')]
final class ProfileNewProjection
{
Expand Down Expand Up @@ -44,12 +41,8 @@ public function drop(): void
}

#[Subscribe(ProfileCreated::class)]
public function handleProfileCreated(Message $message): void
public function handleProfileCreated(ProfileCreated $profileCreated): void
{
$profileCreated = $message->event();

assert($profileCreated instanceof ProfileCreated);

$this->connection->executeStatement(
'INSERT INTO ' . $this->tableName() . ' (id, firstname) VALUES(:id, :firstname);',
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@

use Patchlevel\EventSourcing\Attribute\Processor;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Repository\RepositoryManager;
use Patchlevel\EventSourcing\Tests\Integration\Subscription\Aggregate\Profile;
use Patchlevel\EventSourcing\Tests\Integration\Subscription\Events\ProfileCreated;

use function assert;

#[Processor('profile')]
final class ProfileProcessor
{
Expand All @@ -22,12 +19,8 @@ public function __construct(
}

#[Subscribe(ProfileCreated::class)]
public function handleProfileCreated(Message $message): void
public function handleProfileCreated(ProfileCreated $profileCreated): void
{
$profileCreated = $message->event();

assert($profileCreated instanceof ProfileCreated);

$repository = $this->repositoryManager->get(Profile::class);

$profile = $repository->load($profileCreated->profileId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
use Patchlevel\EventSourcing\Attribute\Setup;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Attribute\Teardown;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
use Patchlevel\EventSourcing\Tests\Integration\Subscription\Events\ProfileCreated;

use function assert;

#[Projector('profile_1')]
final class ProfileProjection
{
Expand Down Expand Up @@ -44,12 +41,8 @@ public function drop(): void
}

#[Subscribe(ProfileCreated::class)]
public function handleProfileCreated(Message $message): void
public function handleProfileCreated(ProfileCreated $profileCreated): void
{
$profileCreated = $message->event();

assert($profileCreated instanceof ProfileCreated);

$this->connection->executeStatement(
'INSERT INTO ' . $this->tableName() . ' (id, name) VALUES(:id, :name);',
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use DateTimeImmutable;
use Patchlevel\EventSourcing\Aggregate\AggregateHeader;
use Patchlevel\EventSourcing\Aggregate\CustomId;
use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Metadata\Subscriber\ArgumentMetadata;
use Patchlevel\EventSourcing\Subscription\Subscriber\ArgumentResolver\AggregateIdArgumentResolver;
Expand All @@ -23,21 +25,21 @@ public function testSupport(): void

self::assertTrue(
$resolver->support(
new ArgumentMetadata('aggregateId', 'string'),
new ArgumentMetadata('aggregateId', Uuid::class),
ProfileCreated::class,
),
);

self::assertTrue(
$resolver->support(
new ArgumentMetadata('aggregateRootId', 'string'),
new ArgumentMetadata('aggregateRootId', ProfileId::class),
ProfileCreated::class,
),
);

self::assertFalse(
$resolver->support(
new ArgumentMetadata('foo', 'string'),
new ArgumentMetadata('foo', ProfileCreated::class),
ProfileCreated::class,
),
);
Expand All @@ -52,10 +54,10 @@ public function testResolve(): void
new AggregateHeader('foo', 'bar', 1, new DateTimeImmutable()),
);

self::assertSame(
'bar',
self::assertEquals(
new CustomId('bar'),
$resolver->resolve(
new ArgumentMetadata('foo', 'string'),
new ArgumentMetadata('foo', CustomId::class),
$message,
),
);
Expand Down

0 comments on commit b681fcb

Please sign in to comment.