Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
24
src/Framework/EventSourcing/AggregateEnvelope.php
Normal file
24
src/Framework/EventSourcing/AggregateEnvelope.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
|
||||
final readonly class AggregateEnvelope
|
||||
{
|
||||
public function __construct(
|
||||
public AggregateId $aggregateId,
|
||||
public object $event,
|
||||
public int $version,
|
||||
public Timestamp $recordedAt,
|
||||
public EventMetadata $metadata,
|
||||
) {
|
||||
}
|
||||
|
||||
public function eventName(): string
|
||||
{
|
||||
return get_class($this->event);
|
||||
}
|
||||
}
|
||||
23
src/Framework/EventSourcing/AggregateId.php
Normal file
23
src/Framework/EventSourcing/AggregateId.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
final readonly class AggregateId
|
||||
{
|
||||
public function __construct(
|
||||
private string $id
|
||||
) {
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public static function generate(): self
|
||||
{
|
||||
return new self(uniqid('aggregate_', true));
|
||||
}
|
||||
}
|
||||
46
src/Framework/EventSourcing/AggregateRoot.php
Normal file
46
src/Framework/EventSourcing/AggregateRoot.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
final readonly class AggregateRoot
|
||||
{
|
||||
public DefaultEventRecorder $eventRecorder;
|
||||
|
||||
public function __construct(public object $state, public AggregateId $id, int $version = 0)
|
||||
{
|
||||
$this->eventRecorder = new DefaultEventRecorder($version);
|
||||
}
|
||||
|
||||
public static function rehydrate(AggregateId $aggregateId, object $state, iterable $events): self
|
||||
{
|
||||
$self = new self($state, $aggregateId);
|
||||
foreach ($events as $event) {
|
||||
$self->apply($event, false);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
public function applyAndRecord(object $event): void
|
||||
{
|
||||
$this->apply($event, true);
|
||||
}
|
||||
|
||||
private function apply(object $event, bool $record): void
|
||||
{
|
||||
$method = 'when' . new \ReflectionClass($event)->getShortName();
|
||||
|
||||
$ref = new \ReflectionObject($this->state);
|
||||
if (! $ref->hasMethod($method)) {
|
||||
throw new \RuntimeException("Missing method $method in state " . $ref->getName());
|
||||
}
|
||||
|
||||
$ref->getMethod($method)->invoke($this->state, $event);
|
||||
|
||||
if ($record) {
|
||||
$this->eventRecorder->record($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/Framework/EventSourcing/AggregateRootRepository.php
Normal file
44
src/Framework/EventSourcing/AggregateRootRepository.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\DateTime\Clock;
|
||||
|
||||
final readonly class AggregateRootRepository
|
||||
{
|
||||
public function __construct(
|
||||
private EventStore $eventStore,
|
||||
private Clock $clock,
|
||||
) {
|
||||
}
|
||||
|
||||
public function load(AggregateId $aggregateId, object $state): AggregateRoot
|
||||
{
|
||||
$events = $this->eventStore->loadStream($aggregateId);
|
||||
|
||||
return AggregateRoot::rehydrate(
|
||||
aggregateId: $aggregateId,
|
||||
state: $state,
|
||||
events: $events
|
||||
);
|
||||
}
|
||||
|
||||
public function save(AggregateRoot $root): void
|
||||
{
|
||||
|
||||
$events = $root->eventRecorder->pullRecordedEvents();
|
||||
|
||||
$ae = new AggregateEnvelope(
|
||||
aggregateId: $root->id,
|
||||
event : $events[0],
|
||||
version : $root->eventRecorder->version,
|
||||
recordedAt : Timestamp::fromClock($this->clock),
|
||||
metadata : new EventMetadata('actor')
|
||||
);
|
||||
|
||||
$this->eventStore->append($ae);
|
||||
}
|
||||
}
|
||||
36
src/Framework/EventSourcing/DefaultEventRecorder.php
Normal file
36
src/Framework/EventSourcing/DefaultEventRecorder.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
final class DefaultEventRecorder implements EventRecorder
|
||||
{
|
||||
/** @var object[] */
|
||||
private array $recordedEvents = [];
|
||||
|
||||
public function __construct(
|
||||
private(set) int $version = 0,
|
||||
) {
|
||||
}
|
||||
|
||||
public function record(object $event): void
|
||||
{
|
||||
$this->recordedEvents[] = $event;
|
||||
$this->version++;
|
||||
}
|
||||
|
||||
/** @return object[] */
|
||||
public function pullRecordedEvents(): array
|
||||
{
|
||||
$events = $this->recordedEvents;
|
||||
$this->clearRecordedEvents();
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
public function clearRecordedEvents(): void
|
||||
{
|
||||
$this->recordedEvents = [];
|
||||
}
|
||||
}
|
||||
19
src/Framework/EventSourcing/Demo/AggregateState.php
Normal file
19
src/Framework/EventSourcing/Demo/AggregateState.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing\Demo;
|
||||
|
||||
final class AggregateState
|
||||
{
|
||||
public bool $isDemo = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function whenDemoAggregateEvent(DemoAggregateEvent $event): void
|
||||
{
|
||||
$this->isDemo = true;
|
||||
}
|
||||
}
|
||||
36
src/Framework/EventSourcing/Demo/DemoAggregate.php
Normal file
36
src/Framework/EventSourcing/Demo/DemoAggregate.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing\Demo;
|
||||
|
||||
use App\Framework\EventSourcing\AggregateId;
|
||||
use App\Framework\EventSourcing\AggregateRoot;
|
||||
|
||||
final readonly class DemoAggregate
|
||||
{
|
||||
public function __construct(
|
||||
public AggregateRoot $root,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function create(AggregateId $aggregateId, DemoAggregateDomainId $id): self
|
||||
{
|
||||
$root = new AggregateRoot(new AggregateState(), $aggregateId);
|
||||
$root->eventRecorder->record(new DemoAggregateEvent($id));
|
||||
|
||||
return new self($root);
|
||||
}
|
||||
|
||||
public static function rehydrate(AggregateId $id, iterable $events): self
|
||||
{
|
||||
$root = AggregateRoot::rehydrate($id, new AggregateState(), $events);
|
||||
|
||||
return new self($root);
|
||||
}
|
||||
|
||||
public function eventHappened(DemoAggregateEvent $event): void
|
||||
{
|
||||
$this->root->applyAndRecord($event);
|
||||
}
|
||||
}
|
||||
38
src/Framework/EventSourcing/Demo/DemoAggregateDomainId.php
Normal file
38
src/Framework/EventSourcing/Demo/DemoAggregateDomainId.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing\Demo;
|
||||
|
||||
final readonly class DemoAggregateDomainId
|
||||
{
|
||||
public function __construct(
|
||||
private string $id
|
||||
) {
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public static function generate(): self
|
||||
{
|
||||
return new self(uniqid('demo_', true));
|
||||
}
|
||||
|
||||
public static function fromString(string $id): self
|
||||
{
|
||||
return new self($id);
|
||||
}
|
||||
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return $this->id === $other->id;
|
||||
}
|
||||
}
|
||||
30
src/Framework/EventSourcing/Demo/DemoAggregateEvent.php
Normal file
30
src/Framework/EventSourcing/Demo/DemoAggregateEvent.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing\Demo;
|
||||
|
||||
final readonly class DemoAggregateEvent
|
||||
{
|
||||
public function __construct(
|
||||
public DemoAggregateDomainId $domainId,
|
||||
public string $name = 'Demo Created',
|
||||
public array $data = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function getDomainId(): DemoAggregateDomainId
|
||||
{
|
||||
return $this->domainId;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
27
src/Framework/EventSourcing/Demo/DemoTest.php
Normal file
27
src/Framework/EventSourcing/Demo/DemoTest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing\Demo;
|
||||
|
||||
use App\Framework\EventSourcing\AggregateId;
|
||||
|
||||
final class DemoTest
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$aggregateId = AggregateId::generate();
|
||||
$domainId = DemoAggregateDomainId::generate();
|
||||
|
||||
$aggregate = DemoAggregate::create($aggregateId, $domainId);
|
||||
|
||||
$aggregate->eventHappened(new DemoAggregateEvent($domainId));
|
||||
|
||||
$aggregate->root->eventRecorder->pullRecordedEvents();
|
||||
|
||||
$rehydrated = DemoAggregate::rehydrate($aggregateId, $aggregate->root->eventRecorder->pullRecordedEvents());
|
||||
|
||||
var_dump("<pre>", $rehydrated);
|
||||
}
|
||||
}
|
||||
15
src/Framework/EventSourcing/EventMetadata.php
Normal file
15
src/Framework/EventSourcing/EventMetadata.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
final readonly class EventMetadata
|
||||
{
|
||||
public function __construct(
|
||||
public string $actor,
|
||||
public ?string $commandId = null,
|
||||
public array $context = []
|
||||
) {
|
||||
}
|
||||
}
|
||||
14
src/Framework/EventSourcing/EventRecorder.php
Normal file
14
src/Framework/EventSourcing/EventRecorder.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
interface EventRecorder
|
||||
{
|
||||
public function record(object $event): void;
|
||||
|
||||
public function pullRecordedEvents(): array;
|
||||
|
||||
public function clearRecordedEvents(): void;
|
||||
}
|
||||
12
src/Framework/EventSourcing/EventStore.php
Normal file
12
src/Framework/EventSourcing/EventStore.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\EventSourcing;
|
||||
|
||||
interface EventStore
|
||||
{
|
||||
public function append(AggregateEnvelope ...$envelopes): void;
|
||||
|
||||
public function loadStream(AggregateId $aggregateId): iterable; //yields AggregateEnvelope
|
||||
}
|
||||
Reference in New Issue
Block a user