Skip to content

Commit

Permalink
Merge pull request #483 from patchlevel/update-docs
Browse files Browse the repository at this point in the history
improve aggregate, clock, event bus and message decorator docs
  • Loading branch information
DavidBadura authored Feb 2, 2024
2 parents a4d782f + c4abcc2 commit 8943c67
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 140 deletions.
4 changes: 2 additions & 2 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
19 changes: 16 additions & 3 deletions docs/pages/aggregate.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Aggregate

The linchpin of event-sourcing is the aggregate. These aggregates can be imagined like entities in ORM.
One main difference is that we don't save the current state, but only the individual events that led to the state.
This means it is always possible to build the state again from the events.
One main difference is that we don't save the current state, but only the individual events that led to the state.
This means it is always possible to build the current state again from the events.

!!! note

Expand Down Expand Up @@ -115,6 +115,7 @@ final class ProfileRegistered
!!! note

You can find out more about events [here](./events.md).
And for normalizer [here](./normalizer.md).

After we have defined the event, we have to adapt the profile aggregate:

Expand Down Expand Up @@ -270,6 +271,10 @@ final class ChangeNameHandler
}
```

!!! success

Our aggregate can now be changed and saved.

!!! note

You can read more about Repository [here](./repository.md).
Expand Down Expand Up @@ -648,4 +653,12 @@ use Patchlevel\EventSourcing\Metadata\AggregateRoot\AttributeAggregateRootRegist
use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;

$aggregateRegistry = (new AttributeEventRegistryFactory())->create($paths);
```
```

## Learn more

* [How to create own aggregate id](aggregate_id.md)
* [How to store and load aggregates](repository.md)
* [How to snapshot aggregates](snapshots.md)
* [How to create Projections](projection.md)
* [How to split streams](split_stream.md)
210 changes: 123 additions & 87 deletions docs/pages/aggregate_id.md
Original file line number Diff line number Diff line change
@@ -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)
13 changes: 10 additions & 3 deletions docs/pages/clock.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Clock

We are using the clock to get the current datetime. This is needed to create the `recorded_on` datetime for the events.
We are using the clock to get the current datetime. This is needed to create the `recorded_on` datetime for the event stream.
We have two implementations of the clock, one for the production and one for the tests.
But you can also create your own implementation that is PSR-20 compatible.
For more information see [here](https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md).

## SystemClock

This uses the native system clock to return the DateTimeImmutable instance - in this case `new DateTimeImmutable()`.
This uses the native system clock to return the `DateTimeImmutable` instance.

```php
use Patchlevel\EventSourcing\Clock\SystemClock;
Expand Down Expand Up @@ -67,4 +67,11 @@ $clock->sleep(10); // sleep 10 seconds

!!! note

The instance of the frozen datetime will be cloned internally, so the it's not the same instance but equals.
The instance of the frozen datetime will be cloned internally, so the it's not the same instance but equals.

## Learn more

* [How to test with datetime](testing.md)
* [How to normalize datetime](normalizer.md)
* [How to use messages](event_bus.md)
* [How to decorate messages](message_decorator.md)
Loading

0 comments on commit 8943c67

Please sign in to comment.