Skip to content

Commit

Permalink
Composite Aggregate IDS
Browse files Browse the repository at this point in the history
- added interface `AggregateIdInterface`
- class `AggregateId` is marked as deprecated and will be removed in next commits
- added interface `CompositeAggregateIdInterface` and trait `CompositeAggregateIdTrait`
- the whole project using now `AggregateIdInterface` instead of `AggregateId` class
- removed event `AggregateDeleted` and trait `DeletableAggregateRootTrait`
- User and Mail domains implements own deleting logic
- added support for composite aggregate ids in event streams
- all domain events now must implement own method `getAggregateId()`
  • Loading branch information
tg666 committed Apr 19, 2024
1 parent 26fac7e commit fe66466
Show file tree
Hide file tree
Showing 51 changed files with 764 additions and 207 deletions.
4 changes: 2 additions & 2 deletions src/ArchitectureBundle/Domain/AggregateRootInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
namespace SixtyEightPublishers\ArchitectureBundle\Domain;

use SixtyEightPublishers\ArchitectureBundle\Domain\Event\AbstractDomainEvent;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateId;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateIdInterface;

interface AggregateRootInterface
{
public function getAggregateId(): AggregateId;
public function getAggregateId(): AggregateIdInterface;

public function getVersion(): int;

Expand Down
4 changes: 2 additions & 2 deletions src/ArchitectureBundle/Domain/AggregateRootTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use RuntimeException;
use SixtyEightPublishers\ArchitectureBundle\Domain\Event\AbstractDomainEvent;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateId;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateIdInterface;
use function array_slice;
use function explode;
use function implode;
Expand All @@ -20,7 +20,7 @@ trait AggregateRootTrait
/** @var array<AbstractDomainEvent> */
protected array $recordedEvents = [];

abstract public function getAggregateId(): AggregateId;
abstract public function getAggregateId(): AggregateIdInterface;

public function getVersion(): int
{
Expand Down
45 changes: 0 additions & 45 deletions src/ArchitectureBundle/Domain/DeletableAggregateRootTrait.php

This file was deleted.

32 changes: 22 additions & 10 deletions src/ArchitectureBundle/Domain/Event/AbstractDomainEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace SixtyEightPublishers\ArchitectureBundle\Domain\Event;

use BackedEnum;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateId;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateIdInterface;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\EventId;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\ValueObjectInterface;
use SixtyEightPublishers\ArchitectureBundle\Event\EventInterface;
Expand Down Expand Up @@ -39,8 +40,13 @@ private function __construct() {}
* @param array<string, mixed> $metadata
* @param array<string, mixed> $parameters
*/
public static function reconstitute(string $eventName, EventId $eventId, DateTimeImmutable $createdAt, array $metadata, array $parameters): static
{
public static function reconstitute(
string $eventName,
EventId $eventId,
DateTimeImmutable $createdAt,
array $metadata,
array $parameters,
): static {
$event = new static(); // @phpstan-ignore-line

$event->eventName = $eventName;
Expand All @@ -52,6 +58,8 @@ public static function reconstitute(string $eventName, EventId $eventId, DateTim
return $event;
}

abstract public function getAggregateId(): AggregateIdInterface;

/**
* @return class-string
*/
Expand All @@ -78,11 +86,6 @@ public function getMetadata(): array
return $this->metadata;
}

public function getAggregateId(): AggregateId
{
return AggregateId::fromNative($this->metadata[self::METADATA_AGGREGATE_ID]);
}

public function getVersion(): int
{
return $this->metadata[self::METADATA_AGGREGATE_VERSION];
Expand Down Expand Up @@ -129,12 +132,17 @@ public function toArray(): array
];
}

protected function getNativeAggregatedId(): mixed
{
return $this->metadata[self::METADATA_AGGREGATE_ID];
}

/**
* @param array<string, mixed> $parameters
*
* @noinspection PhpDocMissingThrowsInspection
*/
protected static function occur(string $aggregateId, array $parameters = []): static
protected static function occur(AggregateIdInterface $aggregateId, array $parameters = []): static
{
$event = new static(); // @phpstan-ignore-line

Expand All @@ -148,6 +156,10 @@ static function (mixed $value): mixed {
return $value->toNative();
}

if ($value instanceof BackedEnum) {
return $value->value;
}

if ($value instanceof DateTimeInterface) {
return $value->format(DateTimeInterface::ATOM);
}
Expand All @@ -162,7 +174,7 @@ static function (mixed $value): mixed {
);

$event->metadata = [
self::METADATA_AGGREGATE_ID => $aggregateId,
self::METADATA_AGGREGATE_ID => $aggregateId->toNative(),
self::METADATA_AGGREGATE_VERSION => 1, // initial
];

Expand Down
22 changes: 0 additions & 22 deletions src/ArchitectureBundle/Domain/Event/AggregateDeleted.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
namespace SixtyEightPublishers\ArchitectureBundle\Domain\Exception;

use DomainException;
use function get_class;
use function gettype;
use function is_object;
use Throwable;
use function sprintf;

final class InvalidNativeValueTypeException extends DomainException
Expand All @@ -18,25 +16,39 @@ final class InvalidNativeValueTypeException extends DomainException
public function __construct(
string $message,
public readonly string $expectedType,
public readonly string $passedType,
public readonly Typehint $passedType,
public readonly string $valueObjectClassname,
?Throwable $previous = null,
) {
parent::__construct($message);
parent::__construct(
message: $message,
previous: $previous,
);
}

/**
* @param class-string $valueObjectClassname
*/
public static function fromNativeValue(mixed $passedNativeValue, string $expectedType, string $valueObjectClassname): self
{
$passedType = is_object($passedNativeValue) ? ('instance of ' . get_class($passedNativeValue)) : gettype($passedNativeValue);
$passedType = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'][$passedType] ?? $passedType;
public static function fromNativeValue(
mixed $passedNativeValue,
string|Typehint $expectedType,
string $valueObjectClassname,
?Throwable $previous = null,
): self {
$passedType = $passedNativeValue instanceof Typehint ? $passedNativeValue : Typehint::fromVariable($passedNativeValue);
$expectedType = (string) $expectedType;

return new self(sprintf(
'Cannot instantiate an value object of the type %s. Expected native value type is %s, %s passed.',
$valueObjectClassname,
$expectedType,
$passedType,
), $expectedType, $passedType, $valueObjectClassname);
return new self(
message: sprintf(
'Cannot instantiate an value object of the type %s. Expected native value type is %s, %s passed.',
$valueObjectClassname,
$expectedType,
$passedType->isInstance ? ('instance of ' . $passedType) : $passedType,
),
expectedType: $expectedType,
passedType: $passedType,
valueObjectClassname: $valueObjectClassname,
previous: $previous,
);
}
}
72 changes: 72 additions & 0 deletions src/ArchitectureBundle/Domain/Exception/Typehint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Domain\Exception;

use stdClass;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_int;
use function is_object;

final class Typehint
{
public function __construct(
public readonly string $value,
public readonly bool $isInstance,
) {}

public static function fromVariable(mixed $variable): self
{
[$value, $isInstance] = self::getVariableType(
variable: $variable,
);

return new self(
value: $value,
isInstance: $isInstance,
);
}

public function __toString(): string
{
return $this->value;
}

/**
* @return array{
* 0: string,
* 1: bool,
* }
*/
private static function getVariableType(mixed $variable): array
{
if (is_object($variable) && stdClass::class !== get_class($variable)) {
$type = get_class($variable);
$isInstance = true;
} else {
$type = gettype($variable);
$isInstance = false;
}

$type = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'][$type] ?? $type;

if ('array' === $type && is_array($variable)) {
$structure = [];

foreach ($variable as $k => $v) {
$structure[] = (is_int($k) ? $k : ($k . "'$k'")) . ': ' . self::getVariableType($v)[0];
}

$type = 'array{' . implode(', ', $structure) . '}';
}

return [
$type,
$isInstance,
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace SixtyEightPublishers\ArchitectureBundle\Domain\Exception;

use DomainException;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateId;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateIdInterface;
use function sprintf;

final class UnableToRecordEventOnDeletedAggregateException extends DomainException
Expand All @@ -16,20 +16,24 @@ final class UnableToRecordEventOnDeletedAggregateException extends DomainExcepti
private function __construct(
string $message,
public readonly string $aggregateClassname,
public readonly AggregateId $aggregateId,
public readonly AggregateIdInterface $aggregateId,
) {
parent::__construct($message);
}

/**
* @param class-string $aggregateClassname
*/
public static function create(string $aggregateClassname, AggregateId $aggregateId): self
public static function create(string $aggregateClassname, AggregateIdInterface $aggregateId): self
{
return new self(sprintf(
'Unable to record event on deleted aggregate %s of type %s',
$aggregateId->toNative(),
$aggregateClassname,
), $aggregateClassname, $aggregateId);
return new self(
message: sprintf(
'Unable to record event on deleted aggregate %s of type %s',
$aggregateId->toString(),
$aggregateClassname,
),
aggregateClassname: $aggregateClassname,
aggregateId: $aggregateId,
);
}
}
5 changes: 4 additions & 1 deletion src/ArchitectureBundle/Domain/ValueObject/AggregateId.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject;

final class AggregateId implements ValueObjectInterface
/**
* @deprecated
*/
final class AggregateId implements AggregateIdInterface
{
use UuidValueTrait;
}
12 changes: 12 additions & 0 deletions src/ArchitectureBundle/Domain/ValueObject/AggregateIdInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject;

interface AggregateIdInterface extends ValueObjectInterface
{
public static function isValid(mixed $native): bool;

public function toString(): string;
}
Loading

0 comments on commit fe66466

Please sign in to comment.