From 80f5c1b8f10cb749c1454709e1ff7e25b69bbad3 Mon Sep 17 00:00:00 2001 From: David Badura Date: Thu, 1 Feb 2024 19:59:59 +0100 Subject: [PATCH] rewrite aggregate id docs --- docs/mkdocs.yml | 4 +- docs/pages/aggregate_id.md | 210 ++++++++++++++++------------ docs/pages/{tests.md => testing.md} | 0 3 files changed, 125 insertions(+), 89 deletions(-) rename docs/pages/{tests.md => testing.md} (100%) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6086180d6..0b14915ef 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -86,6 +86,7 @@ nav: - Processor: processor.md - Projection: projection.md - Advanced: + - Aggregate ID: aggregate_id.md - Normalizer: normalizer.md - Snapshots: snapshots.md - Upcasting: upcasting.md @@ -94,9 +95,8 @@ nav: - Message Decorator: message_decorator.md - Split Stream: split_stream.md - Time / Clock: clock.md + - Testing: testing.md - Other / Tools: - - UUID: uuid.md - CLI: cli.md - Schema Migration: migration.md - Watch Server: watch_server.md - - Tests: tests.md diff --git a/docs/pages/aggregate_id.md b/docs/pages/aggregate_id.md index e44000a8a..dc0219074 100644 --- a/docs/pages/aggregate_id.md +++ b/docs/pages/aggregate_id.md @@ -1,135 +1,171 @@ -# UUID +# Aggregate ID -A UUID can be generated for the `aggregateId`. There are two popular libraries that can be used: +The `aggregate id` is a unique identifier for an aggregate. +It is used to identify the aggregate in the event store. +The `aggregate` does not care how the id is generated, +since only an aggregate-wide unique string is expected in the store. -* [ramsey/uuid](https://github.com/ramsey/uuid) -* [symfony/uid](https://symfony.com/doc/current/components/uid.html) +This library provides you with a few options for generating the id. -The `aggregate` does not care how the id is generated, since only an aggregate-wide unique string is expected here. +## Uuid + +The easiest way is to use an `uuid` as an aggregate ID. +For this, we have the `Uuid` class, which is a simple wrapper for the [ramsey/uuid](https://github.com/ramsey/uuid) library. + +You can use it like this: ```php use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot; +use Patchlevel\EventSourcing\Aggregate\Uuid; use Patchlevel\EventSourcing\Attribute\Aggregate; use Patchlevel\EventSourcing\Attribute\Apply; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; +use Patchlevel\EventSourcing\Attribute\Id; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; #[Aggregate('profile')] final class Profile extends BasicAggregateRoot { - private UuidInterface $id; - private string $name; + #[Id] + #[IdNormalizer(Uuid::class)] + private Uuid $id; +} +``` - public function aggregateRootId(): string - { - return $this->id->toString(); - } - - public function id(): UuidInterface - { - return $this->id; - } - - public function name(): string - { - return $this->name; - } +!!! note - public static function create(string $name): self - { - $id = Uuid::uuid4(); - - $self = new self(); - $self->recordThat(new ProfileCreated($id, $name)); + If you want to use snapshots, then you have to make sure that the aggregate id are normalized. + You can find how to do this [here](normalizer.md). - return $self; - } - - #[Apply] - protected function applyProfileCreated(ProfileCreated $event): void - { - $this->id = $event->profileId(); - $this->name = $event->name(); - } +You have multiple options for generating an uuid: + +```php +use Patchlevel\EventSourcing\Aggregate\Uuid; + +$uuid = Uuid::v6(); +$uuid = Uuid::v7(); +$uuid = Uuid::fromString('d6e8d7a0-4b0b-4e6a-8a9a-3a0b2d9d0e4e'); +``` + +!!! Note + + We offer you the uuid versions 6 and 7, because they are the most suitable for event sourcing. + More information about uuid versions can be found [here](https://uuid.ramsey.dev/en/stable/rfc4122.html). + +## Custom ID + +If you don't want to use an uuid, you can also use the custom ID implementation. +This is a value object that holds any string. + +```php +use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot; +use Patchlevel\EventSourcing\Aggregate\CustomId; +use Patchlevel\EventSourcing\Attribute\Aggregate; +use Patchlevel\EventSourcing\Attribute\Apply; +use Patchlevel\EventSourcing\Attribute\Id; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; + +#[Aggregate('profile')] +final class Profile extends BasicAggregateRoot +{ + #[Id] + #[IdNormalizer(CustomId::class)] + private CustomId $id; } ``` -Or even better, you create your own aggregate-specific id class. +!!! note + + If you want to use snapshots, then you have to make sure that the aggregate id are normalized. + You can find how to do this [here](normalizer.md). + +So you can use any string as an id: + +```php +use Patchlevel\EventSourcing\Aggregate\CustomId; + +$id = CustomId::fromString('my-id'); +``` + +## Implement own ID + +Or even better, you create your own aggregate-specific ID class. This allows you to ensure that the correct id is always used. The whole thing looks like this: ```php -use Ramsey\Uuid\Uuid; +use Patchlevel\EventSourcing\Aggregate\AggregateRootId; -class ProfileId +class ProfileId implements AggregateRootId { - private string $id; - - public function __construct(string $id) - { - $this->id = $id; - } - - public static function generate(): self - { - return new self(Uuid::uuid4()->toString()); + private function __construct( + private readonly string $id + ){ } - + public function toString(): string { return $this->id; } + + public static function fromString(string $id): self + { + return new self($id); + } } ``` +So you can use it like this: + ```php use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot; use Patchlevel\EventSourcing\Attribute\Aggregate; use Patchlevel\EventSourcing\Attribute\Apply; - -use Ramsey\Uuid\UuidInterface; +use Patchlevel\EventSourcing\Attribute\Id; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; #[Aggregate('profile')] final class Profile extends BasicAggregateRoot { + #[Id] + #[IdNormalizer(ProfileId::class)] private ProfileId $id; - private string $name; +} +``` - public function aggregateRootId(): string - { - return $this->id->toString(); - } - - public function id(): ProfileId - { - return $this->id; - } - - public function name(): string - { - return $this->name; - } +!!! note - public static function create(string $name): self - { - $id = ProfileId::generate(); - - $self = new self(); - $self->recordThat(new ProfileCreated($id, $name)); + If you want to use snapshots, then you have to make sure that the aggregate id are normalized. + You can find how to do this [here](normalizer.md). - return $self; - } - - #[Apply] - protected function applyProfileCreated(ProfileCreated $event): void - { - $this->id = $event->profileId(); - $this->name = $event->name(); - } +We also offer you some traits, so that you don't have to implement the `AggregateRootId` interface yourself. +Here for the uuid: + +```php +use Patchlevel\EventSourcing\Aggregate\AggregateRootId; +use Patchlevel\EventSourcing\Aggregate\RamseyUuidBehaviour; +use Patchlevel\EventSourcing\Aggregate\Uuid; + +class ProfileId implements AggregateRootId +{ + use RamseyUuidBehaviour; } ``` -!!! note +Or for the custom id: - If you want to use snapshots, then you have to make sure that the value objects are normalized. - You can find how to do this [here](normalizer.md). +```php +use Patchlevel\EventSourcing\Aggregate\AggregateRootId; +use Patchlevel\EventSourcing\Aggregate\CustomIdBehaviour; + +class ProfileId implements AggregateRootId +{ + use CustomIdBehaviour; +} +``` + +### Learn more + +* [How to create an aggregate](aggregate.md) +* [How to create an event](events.md) +* [How to test an aggregate](testing.md) +* [How to normalize value objects](normalizer.md) \ No newline at end of file diff --git a/docs/pages/tests.md b/docs/pages/testing.md similarity index 100% rename from docs/pages/tests.md rename to docs/pages/testing.md