Skip to content

Commit d6031a1

Browse files
authored
Release/2.1.0 (#6)
1 parent 526fe71 commit d6031a1

41 files changed

Lines changed: 847 additions & 565 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 249 additions & 265 deletions
Large diffs are not rendered by default.

src/Aggregate/AggregateRoot.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,17 @@ interface AggregateRoot extends Entity
3939
*
4040
* @return SequenceNumber The current sequence number.
4141
*/
42-
public function getSequenceNumber(): SequenceNumber;
42+
public function sequenceNumber(): SequenceNumber;
4343

4444
/**
4545
* Returns the schema version of this aggregate type.
4646
*
47-
* <p>Resolved from the protected <code>modelVersion()</code> method, defaults to <code>0</code>
48-
* when the method is not overridden. Used by consumers to migrate aggregate schemas when loading older
49-
* persisted state.</p>
47+
* <p>Defaults to <code>ModelVersion::initial()</code> (value 0) when not overridden. Used by consumers
48+
* to migrate aggregate schemas when loading older persisted state.</p>
5049
*
51-
* @return SequenceNumber The declared model version, or 0 when not overridden.
50+
* @return ModelVersion The declared model version.
5251
*/
53-
public function getModelVersion(): SequenceNumber;
52+
public function modelVersion(): ModelVersion;
5453

5554
/**
5655
* Returns the short class name of this aggregate.
@@ -60,5 +59,5 @@ public function getModelVersion(): SequenceNumber;
6059
*
6160
* @return string The short class name.
6261
*/
63-
public function buildAggregateName(): string;
62+
public function aggregateName(): string;
6463
}

src/Aggregate/AggregateRootBehavior.php

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use TinyBlocks\BuildingBlocks\Event\EventRecords;
1313
use TinyBlocks\BuildingBlocks\Event\EventType;
1414
use TinyBlocks\BuildingBlocks\Event\SequenceNumber;
15-
use TinyBlocks\BuildingBlocks\Event\SnapshotData;
15+
use TinyBlocks\BuildingBlocks\Snapshot\SnapshotData;
1616
use TinyBlocks\Time\Instant;
1717

1818
trait AggregateRootBehavior
@@ -23,44 +23,44 @@ trait AggregateRootBehavior
2323

2424
private SequenceNumber $sequenceNumber;
2525

26-
public function getSequenceNumber(): SequenceNumber
26+
public function sequenceNumber(): SequenceNumber
2727
{
2828
return $this->sequenceNumber ?? SequenceNumber::initial();
2929
}
3030

31-
public function getModelVersion(): SequenceNumber
31+
public function modelVersion(): ModelVersion
3232
{
33-
return SequenceNumber::of(value: $this->modelVersion());
33+
return ModelVersion::initial();
3434
}
3535

36-
public function buildAggregateName(): string
36+
public function aggregateName(): string
3737
{
38-
return new ReflectionClass(static::class)->getShortName();
38+
return new ReflectionClass(objectOrClass: static::class)->getShortName();
3939
}
4040

41-
protected function modelVersion(): int
41+
protected function nextSequenceNumber(): void
4242
{
43-
return 0;
43+
$this->sequenceNumber = $this->sequenceNumber()->next();
4444
}
4545

46-
protected function nextSequenceNumber(): void
46+
protected function generateSnapshotData(): SnapshotData
4747
{
48-
$this->sequenceNumber = $this->getSequenceNumber()->next();
48+
return new SnapshotData(payload: $this->snapshotState());
4949
}
5050

51-
public function recordedEvents(): EventRecords
51+
protected function snapshotState(): array
5252
{
53-
$records = $this->recordedEvents ?? EventRecords::createFromEmpty();
53+
$state = get_object_vars($this);
54+
unset($state['recordedEvents'], $state['sequenceNumber']);
5455

55-
return EventRecords::createFrom(elements: $records);
56+
return $state;
5657
}
5758

58-
protected function generateSnapshotData(): SnapshotData
59+
public function recordedEvents(): EventRecords
5960
{
60-
$state = get_object_vars($this);
61-
unset($state['recordedEvents']);
61+
$records = $this->recordedEvents ?? EventRecords::createFromEmpty();
6262

63-
return new SnapshotData(payload: $state);
63+
return EventRecords::createFrom(elements: $records);
6464
}
6565

6666
protected function buildEventRecord(DomainEvent $event): EventRecord
@@ -69,12 +69,12 @@ protected function buildEventRecord(DomainEvent $event): EventRecord
6969
id: Uuid::uuid4(),
7070
type: EventType::fromEvent(event: $event),
7171
event: $event,
72-
identity: $this->getIdentity(),
72+
identity: $this->identity(),
7373
revision: $event->revision(),
7474
occurredOn: Instant::now(),
7575
snapshotData: $this->generateSnapshotData(),
76-
aggregateType: $this->buildAggregateName(),
77-
sequenceNumber: $this->getSequenceNumber()
76+
aggregateType: $this->aggregateName(),
77+
sequenceNumber: $this->sequenceNumber()
7878
);
7979
}
8080
}

src/Aggregate/EventSourcingRoot.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function recordedEvents(): EventRecords;
5454
*
5555
* @param Identity $identity The identity to assign to the new aggregate.
5656
* @return static A new aggregate in its initial state.
57-
* @throws MissingIdentityProperty When the property referenced by <code>identityName()</code> does not exist.
57+
* @throws MissingIdentityProperty When the property referenced by <code>identityProperty()</code> does not exist.
5858
*/
5959
public static function blank(Identity $identity): static;
6060

@@ -69,7 +69,7 @@ public static function blank(Identity $identity): static;
6969
* @param iterable<EventRecord> $records The event stream to replay, ordered by sequence number.
7070
* @param Snapshot|null $snapshot Optional snapshot to restore from before replay.
7171
* @return static The reconstituted aggregate.
72-
* @throws MissingIdentityProperty When the property referenced by <code>identityName()</code> does not exist.
72+
* @throws MissingIdentityProperty When the property referenced by <code>identityProperty()</code> does not exist.
7373
*/
7474
public static function reconstitute(Identity $identity, iterable $records, ?Snapshot $snapshot = null): static;
7575

@@ -83,12 +83,12 @@ public static function reconstitute(Identity $identity, iterable $records, ?Snap
8383
*
8484
* @return array<string, mixed> Keyed by property name.
8585
*/
86-
public function getSnapshotState(): array;
86+
public function snapshotState(): array;
8787

8888
/**
8989
* Restores aggregate state from the given snapshot.
9090
*
91-
* <p>Implementations read {@see Snapshot::getAggregateState()} and copy the relevant fields into
91+
* <p>Implementations read {@see Snapshot::aggregateState()} and copy the relevant fields into
9292
* their own properties. The sequence number is applied automatically by
9393
* <code>reconstitute()</code>; implementations should not touch it.</p>
9494
*

src/Aggregate/EventSourcingRootBehavior.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static function reconstitute(
3939

4040
if (!is_null($snapshot)) {
4141
$aggregate->applySnapshot(snapshot: $snapshot);
42-
$aggregate->sequenceNumber = $snapshot->getSequenceNumber();
42+
$aggregate->sequenceNumber = $snapshot->sequenceNumber();
4343
}
4444

4545
foreach ($records as $record) {
@@ -54,7 +54,7 @@ public function eventHandlers(): array
5454
return [];
5555
}
5656

57-
public function getSnapshotState(): array
57+
public function snapshotState(): array
5858
{
5959
$state = get_object_vars($this);
6060
unset($state['recordedEvents'], $state['sequenceNumber']);

src/Aggregate/EventualAggregateRoot.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@
1010
* Aggregate root variant that records domain events for eventual publication via transactional outbox.
1111
*
1212
* <p>State is persisted as the source of truth; events are emitted as side effects and delivered
13-
* at-least-once to external consumers. The repository is expected to drain
14-
* <code>recordedEvents()</code> after persisting the aggregate state and then call
15-
* <code>clearRecordedEvents()</code> to reset the buffer for the next unit of work.</p>
13+
* at-least-once to external consumers. The repository drains <code>recordedEvents()</code> after
14+
* persisting the aggregate state.</p>
15+
*
16+
* <p><strong>Use-once contract:</strong> the recorded-events buffer is never cleared. After the
17+
* repository drains <code>recordedEvents()</code> and persists the records to the outbox, the aggregate
18+
* instance must be discarded. Re-saving the same instance attempts to push the same envelopes again and
19+
* fails with a duplicate-event error from the outbox. Applications that need to perform multiple
20+
* operations on the same logical aggregate within one process must reload from the repository between
21+
* operations.</p>
1622
*
1723
* <p>Sibling of {@see EventSourcingRoot}, not a parent. Outbox and event sourcing are mutually exclusive
1824
* persistence strategies: an aggregate either persists its state and emits events as side effects, or
@@ -25,19 +31,12 @@
2531
interface EventualAggregateRoot extends AggregateRoot
2632
{
2733
/**
28-
* Returns a copy of the events recorded since the last clear.
34+
* Returns a copy of all events recorded since the aggregate was created.
2935
*
3036
* <p>Always returns a fresh copy: external mutation of the returned collection does not leak into the
3137
* aggregate's internal buffer.</p>
3238
*
3339
* @return EventRecords A snapshot of the recorded events, safe to iterate and mutate.
3440
*/
3541
public function recordedEvents(): EventRecords;
36-
37-
/**
38-
* Discards all recorded events.
39-
*
40-
* <p>Typically called by the repository after the events have been persisted to the outbox.</p>
41-
*/
42-
public function clearRecordedEvents(): void;
4342
}

src/Aggregate/EventualAggregateRootBehavior.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ trait EventualAggregateRootBehavior
1111
{
1212
use AggregateRootBehavior;
1313

14-
public function clearRecordedEvents(): void
15-
{
16-
$this->recordedEvents = EventRecords::createFromEmpty();
17-
}
18-
1914
protected function push(DomainEvent $event): void
2015
{
2116
$this->nextSequenceNumber();

src/Aggregate/ModelVersion.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TinyBlocks\BuildingBlocks\Aggregate;
6+
7+
use TinyBlocks\BuildingBlocks\Internal\Exceptions\InvalidModelVersion;
8+
use TinyBlocks\Vo\ValueObject;
9+
use TinyBlocks\Vo\ValueObjectBehavior;
10+
11+
final readonly class ModelVersion implements ValueObject
12+
{
13+
use ValueObjectBehavior;
14+
15+
private function __construct(public int $value)
16+
{
17+
if ($value < 0) {
18+
throw new InvalidModelVersion(value: $value);
19+
}
20+
}
21+
22+
public static function of(int $value): ModelVersion
23+
{
24+
return new ModelVersion(value: $value);
25+
}
26+
27+
public static function initial(): ModelVersion
28+
{
29+
return new ModelVersion(value: 0);
30+
}
31+
}

src/Entity/CompoundIdentity.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* unique (for example <code>(tenantId, appointmentId)</code> in multi-tenant contexts). Not a concept
1212
* from Evans.</p>
1313
*
14-
* <p>All declared properties participate in the identity: <code>getIdentityValue()</code> returns them
14+
* <p>All declared properties participate in the identity: <code>identityValue()</code> returns them
1515
* as an associative array keyed by property name.</p>
1616
*/
1717
interface CompoundIdentity extends Identity

src/Entity/CompoundIdentityBehavior.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ trait CompoundIdentityBehavior
1010
{
1111
use ValueObjectBehavior;
1212

13-
public function getIdentityValue(): mixed
13+
public function identityValue(): mixed
1414
{
1515
return get_object_vars($this);
1616
}

0 commit comments

Comments
 (0)