Skip to content

Commit

Permalink
Merge pull request #612 from patchlevel/infer-aggregate-id-normalizer
Browse files Browse the repository at this point in the history
infer aggregate id normalizer
  • Loading branch information
DavidBadura authored Jul 9, 2024
2 parents 34c9084 + 3cd1048 commit a87ed93
Show file tree
Hide file tree
Showing 29 changed files with 106 additions and 87 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"doctrine/dbal": "^3.8.1|^4.0.0",
"doctrine/migrations": "^3.3.2",
"patchlevel/hydrator": "^1.3.1",
"patchlevel/hydrator": "^1.4.1",
"patchlevel/worker": "^1.2.0",
"psr/cache": "^2.0.0|^3.0.0",
"psr/clock": "^1.0",
Expand Down
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions deptrac-baseline.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
deptrac:
skip_violations:
Patchlevel\EventSourcing\Aggregate\AggregateRootId:
- Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer
Patchlevel\EventSourcing\Aggregate\CustomId:
- Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer
Patchlevel\EventSourcing\Aggregate\Uuid:
- Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer
Patchlevel\EventSourcing\Attribute\Processor:
- Patchlevel\EventSourcing\Subscription\RunMode
Patchlevel\EventSourcing\Attribute\Projector:
Expand Down
8 changes: 2 additions & 6 deletions docs/pages/aggregate.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,16 @@ final class CreateProfileHandler

In order that an aggregate is actually saved, at least one event must exist in the DB.
For our aggregate we create the Event `ProfileRegistered` with an ID and a name.
Since the ID is a complex data type and cannot be easily serialized, we need to define a normalizer for the ID.
We do this with the `IdNormalizer` attribute.
We also give the event a unique name using the `Event` attribute.

```php
use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Attribute\Event;
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;

#[Event('profile.registered')]
final class ProfileRegistered
{
public function __construct(
#[IdNormalizer]
public readonly Uuid $profileId,
public readonly string $name,
) {
Expand All @@ -114,7 +110,6 @@ 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 @@ -489,7 +484,8 @@ final class NameChanged
```
!!! warning

The payload must be serializable and unserializable as json.
You need to create a normalizer for the `Name` value object.
So the payload must be serializable and unserializable as json.

!!! note

Expand Down
22 changes: 0 additions & 22 deletions docs/pages/aggregate_id.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,14 @@ use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot;
use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Attribute\Aggregate;
use Patchlevel\EventSourcing\Attribute\Id;
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;

#[Aggregate('profile')]
final class Profile extends BasicAggregateRoot
{
#[Id]
#[IdNormalizer]
private Uuid $id;
}
```
!!! 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).

You have multiple options for generating an uuid:

```php
Expand All @@ -63,13 +56,11 @@ use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot;
use Patchlevel\EventSourcing\Aggregate\CustomId;
use Patchlevel\EventSourcing\Attribute\Aggregate;
use Patchlevel\EventSourcing\Attribute\Id;
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;

#[Aggregate('profile')]
final class Profile extends BasicAggregateRoot
{
#[Id]
#[IdNormalizer]
private CustomId $id;
}
```
Expand All @@ -79,11 +70,6 @@ final class Profile extends BasicAggregateRoot
you need to change the `aggregate_id_type` to `string` in the store configuration.
More information can be found [here](store.md).

!!! 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
Expand Down Expand Up @@ -124,21 +110,14 @@ So you can use it like this:
use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot;
use Patchlevel\EventSourcing\Attribute\Aggregate;
use Patchlevel\EventSourcing\Attribute\Id;
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;

#[Aggregate('profile')]
final class Profile extends BasicAggregateRoot
{
#[Id]
#[IdNormalizer]
private ProfileId $id;
}
```
!!! 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).

We also offer you some traits, so that you don't have to implement the `AggregateRootId` interface yourself.
Here for the uuid:

Expand Down Expand Up @@ -167,4 +146,3 @@ class ProfileId implements AggregateRootId
* [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)
5 changes: 5 additions & 0 deletions docs/pages/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ final class ProfileCreated
}
}
```
!!! tip

Built-in normalizers like `IdNormalizer` and `DateTimeImmutableNormalizer` can be inferred from the type hint
and so you don't have to specify them. If you want to configure the Normalizer, you still have to do it.

!!! note

You can find out more about normalizer [here](normalizer.md).
Expand Down
2 changes: 0 additions & 2 deletions docs/pages/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ A hotel can be created with a `name` and a `id`:
```php
use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Attribute\Event;
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;

#[Event('hotel.created')]
final class HotelCreated
{
public function __construct(
#[IdNormalizer]
public readonly Uuid $hotelId,
public readonly string $hotelName,
) {
Expand Down
87 changes: 79 additions & 8 deletions docs/pages/normalizer.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Normalizer

Sometimes you also want to add more complex data in events as payload or in aggregates for the snapshots.
For example DateTime, enums or value objects. You can do that too.
However, you must define a normalizer for this so that the library knows
how to write this data to the database and load it again.
For example DateTime, enums or value objects.
Here you can use the normalizer to define how the data should be saved and loaded.

!!! note

Expand All @@ -12,7 +11,30 @@ how to write this data to the database and load it again.

## Usage

You have to set the normalizer to the properties using the specific normalizer class.
You have a lot of options to use the normalizer.
First of all and simplest, you can let guess the normalizer from the type hint.

```php
final class DTO
{
public DateTimeImmutable $date;
}
```
Most built-in normalizers can be inferred from the type hint:

* `DateTimeImmutable` => `DateTimeImmutableNormalizer`
* `DateTime` => `DateTimeNormalizer`
* `DateTimeZone` => `DateTimeZoneNormalizer`
* `Enum` => `EnumNormalizer`
* `AggregateRootId` => `IdNormalizer`

!!! note

`ObjectNormalizer` will not be inferred. You have to specify it yourself.
This should prevent you from accidentally serializing objects that you don't want to serialize.

The other way is to specify the normalizer to the properties directly.
This example is equivalent to the previous one.

```php
use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer;
Expand All @@ -23,7 +45,7 @@ final class DTO
public DateTimeImmutable $date;
}
```
The whole thing also works with property promotion and readonly properties.
And the whole thing also works with property promotion and readonly properties too.

```php
use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer;
Expand All @@ -37,9 +59,25 @@ final class DTO
}
}
```
!!! tip
If you have child entities or value objects, then you can also define the normalizer on class level.
So you don't have to specify it for each property.

If you have personal data, you can use [crypto-shredding](personal_data.md).
```php
use Patchlevel\Hydrator\Normalizer\ObjectNormalizer;

#[ObjectNormalizer]
final class Item
{
public function __construct(
public readonly int $number,
public readonly DateTimeImmutable $addedAt,
) {
}
}
```
!!! note

With the `ObjectNormalizer`, you can seraialize and deserialize recursively.

### Event

Expand All @@ -61,6 +99,10 @@ final class CreateHotel
}
}
```
!!! note

If you have personal data, you can use [crypto-shredding](personal_data.md).

### Aggregate

For the aggregates it is very similar to the events. However, the normalizer is only used for the snapshots.
Expand Down Expand Up @@ -126,6 +168,10 @@ final class DTO
public DateTimeImmutable $date;
}
```
!!! tip

You can let the hydrator guess the normalizer from the type hint.

You can also define the format. Either describe it yourself as a string or use one of the existing constants.
The default is `DateTimeImmutable::ATOM`.

Expand Down Expand Up @@ -155,6 +201,10 @@ final class DTO
public DateTime $date;
}
```
!!! tip

You can let the hydrator guess the normalizer from the type hint.

You can also specify the format here. The default is `DateTime::ATOM`.

```php
Expand Down Expand Up @@ -188,6 +238,10 @@ final class DTO
public DateTimeZone $timeZone;
}
```
!!! tip

You can let the hydrator guess the normalizer from the type hint.

### Enum

Backed enums can also be normalized.
Expand All @@ -201,6 +255,10 @@ final class DTO
public Status $status;
}
```
!!! tip

You can let the hydrator guess the normalizer from the type hint.

You can also specify the enum class.

```php
Expand All @@ -226,6 +284,10 @@ final class DTO
public Uuid $id;
}
```
!!! tip

You can let the hydrator guess the normalizer from the type hint.

Optional you can also define the type of the id.

```php
Expand Down Expand Up @@ -295,7 +357,7 @@ Finally, you have to allow the normalizer to be used as an attribute.
use Patchlevel\Hydrator\Normalizer\InvalidArgument;
use Patchlevel\Hydrator\Normalizer\Normalizer;

#[Attribute(Attribute::TARGET_PROPERTY)]
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)]
class NameNormalizer implements Normalizer
{
public function normalize(mixed $value): string
Expand Down Expand Up @@ -338,6 +400,15 @@ final class DTO

Every normalizer, including the custom normalizer, can be used both for the events and for the snapshots.

Or define it on class level, so you don't have to specify it for each property.

```php
#[NameNormalizer]
final class Name
{
/* name logic... */
}
```
## Normalized Name

By default, the property name is used to name the field in the normalized result.
Expand Down
4 changes: 0 additions & 4 deletions docs/pages/snapshots.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,14 @@ use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Attribute\Aggregate;
use Patchlevel\EventSourcing\Attribute\Id;
use Patchlevel\EventSourcing\Attribute\Snapshot;
use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;
use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer;

#[Aggregate('profile')]
#[Snapshot('default')]
final class Profile extends BasicAggregateRoot
{
#[Id]
#[IdNormalizer]
public Uuid $id;
public string $name;
#[DateTimeImmutableNormalizer]
public DateTimeImmutable $createdAt;

// ...
Expand Down
Loading

0 comments on commit a87ed93

Please sign in to comment.