Skip to content

DCB #763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: 3.12.x
Choose a base branch
from
Draft

DCB #763

Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ phpunit-integration: vendor

.PHONY: phpunit-integration-postgres
phpunit-integration-postgres: vendor ## run phpunit integration tests on postgres
DB_URL="pdo-pgsql://postgres:postgres@localhost:5432/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration
DB_URL="pdo-pgsql://postgres:postgres@127.0.0.1:5432/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration

.PHONY: phpunit-integration-mysql
phpunit-integration-mysql: vendor ## run phpunit integration tests on mysql
DB_URL="pdo-mysql://root@localhost:3306/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration
DB_URL="pdo-mysql://root@127.0.0.1:3306/eventstore?charset=utf8" vendor/bin/phpunit --testsuite=integration

.PHONY: phpunit-unit
phpunit-unit: vendor ## run phpunit unit tests
Expand Down Expand Up @@ -74,7 +74,7 @@ test: phpunit

.PHONY: benchmark
benchmark: vendor ## run benchmarks
DB_URL=sqlite3:///:memory: php -d memory_limit=512M vendor/bin/phpbench run tests/Benchmark --report=default
DB_URL=sqlite3:///:memory: php -d memory_limit=512M vendor/bin/phpbench run tests/Benchmark --report=default --filter=benchAppend100Events

.PHONY: benchmark-base
benchmark-base: vendor ## run benchmarks
Expand Down
169 changes: 86 additions & 83 deletions composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ nav:
- Event Bus: event_bus.md
- Query Bus: query_bus.md
- Advanced:
- Dynamic Consistency Boundary: dynamic_consistency_boundary.md
- Aggregate ID: aggregate_id.md
- Normalizer: normalizer.md
- Snapshots: snapshots.md
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/dynamic_consistency_boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Dynamic Consistency Boundary

5 changes: 3 additions & 2 deletions src/Aggregate/AggregateRootId.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
namespace Patchlevel\EventSourcing\Aggregate;

use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer;
use Patchlevel\EventSourcing\Stringable;

#[IdNormalizer]
interface AggregateRootId
interface AggregateRootId extends Stringable
{
public function toString(): string;

public static function fromString(string $id): self;
public static function fromString(string $value): self;
}
4 changes: 2 additions & 2 deletions src/Aggregate/CustomIdBehaviour.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public function __construct(
) {
}

public static function fromString(string $id): self
public static function fromString(string $value): self
{
return new self($id);
return new self($value);
}

public function toString(): string
Expand Down
4 changes: 2 additions & 2 deletions src/Aggregate/RamseyUuidV7Behaviour.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public function __construct(
) {
}

public static function fromString(string $id): self
public static function fromString(string $value): self
{
return new self(Uuid::fromString($id));
return new self(Uuid::fromString($value));
}

public function toString(): string
Expand Down
17 changes: 17 additions & 0 deletions src/Attribute/EventTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
final class EventTag
{
public function __construct(
public readonly string|null $prefix = null,
public readonly string|null $hash = null,
) {
}
}
69 changes: 69 additions & 0 deletions src/DCB/AttributeEventTagExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\DCB;

use Patchlevel\EventSourcing\Attribute\EventTag;
use Patchlevel\EventSourcing\Stringable;
use ReflectionClass;
use RuntimeException;
use Stringable as NativeStringable;

use function array_keys;
use function get_debug_type;
use function hash;
use function is_int;
use function is_string;
use function sprintf;

/** @experimental */
final class AttributeEventTagExtractor implements EventTagExtractor
{
/** @return list<string> */
public function extract(object $event): array
{
$reflectionClass = new ReflectionClass($event);

$tags = [];

foreach ($reflectionClass->getProperties() as $property) {
$attributes = $property->getAttributes(EventTag::class);

if ($attributes === []) {
continue;
}

/** @var EventTag $attribute */
$attribute = $attributes[0]->newInstance();

$value = $property->getValue($event);

if ($value instanceof Stringable) {
$value = $value->toString();
}

if ($value instanceof NativeStringable || is_int($value)) {
$value = (string)$value;
}

if (!is_string($value)) {
throw new RuntimeException(
sprintf('Event tag value must be stringable, %s given', get_debug_type($value)),
);
}

if ($attribute->hash) {

Check failure on line 56 in src/DCB/AttributeEventTagExtractor.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

RiskyTruthyFalsyComparison

src/DCB/AttributeEventTagExtractor.php:56:17: RiskyTruthyFalsyComparison: Operand of type null|string contains type string, which can be falsy and truthy. This can cause possibly unexpected behavior. Use strict comparison instead. (see https://psalm.dev/356)
$value = hash($attribute->hash, $value);
}

if ($attribute->prefix) {

Check failure on line 60 in src/DCB/AttributeEventTagExtractor.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

RiskyTruthyFalsyComparison

src/DCB/AttributeEventTagExtractor.php:60:17: RiskyTruthyFalsyComparison: Operand of type null|string contains type string, which can be falsy and truthy. This can cause possibly unexpected behavior. Use strict comparison instead. (see https://psalm.dev/356)
$value = $attribute->prefix . ':' . $value;
}

$tags[$value] = true;
}

return array_keys($tags);
}
}
51 changes: 51 additions & 0 deletions src/DCB/CompositeProjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\DCB;

use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Store\Query;

use function array_map;

/**
* @experimental
* @extends Projection<array<string, mixed>>
*/
final class CompositeProjection

Check failure on line 16 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

InvalidDocblock

src/DCB/CompositeProjection.php:16:1: InvalidDocblock: @template-extends must include the name of an extended class, got Patchlevel\EventSourcing\DCB\Projection<array<string, mixed>> (see https://psalm.dev/008)

Check failure on line 16 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by PHPStan (locked, 8.3, ubuntu-latest)

Class Patchlevel\EventSourcing\DCB\CompositeProjection has @extends tag, but does not extend any class.
{
/** @param array<string, Projection> $projections */
public function __construct(
private readonly array $projections,
) {
}

public function query(): Query
{
$query = new Query();

foreach ($this->projections as $projection) {
$query = $query->add($projection->subQuery());
}

return $query;
}

/** @return array<string, mixed> */
public function initialState(): array
{
return array_map(static function (Projection $projection) {

Check failure on line 38 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

MissingClosureReturnType

src/DCB/CompositeProjection.php:38:26: MissingClosureReturnType: Closure does not have a return type, expecting mixed (see https://psalm.dev/068)
return $projection->initialState();
}, $this->projections);
}

public function apply(mixed $state, Message $message): mixed
{
foreach ($this->projections as $name => $projection) {
$state[$name] = $projection->apply($state[$name], $message);

Check failure on line 46 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

MixedArrayAccess

src/DCB/CompositeProjection.php:46:48: MixedArrayAccess: Cannot access array value on mixed variable $state (see https://psalm.dev/051)

Check failure on line 46 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

MixedArrayAssignment

src/DCB/CompositeProjection.php:46:13: MixedArrayAssignment: Cannot access array value on mixed variable $state[$name] (see https://psalm.dev/117)

Check failure on line 46 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by Psalm (locked, 8.3, ubuntu-latest)

MixedAssignment

src/DCB/CompositeProjection.php:46:13: MixedAssignment: Unable to determine the type of this assignment (see https://psalm.dev/032)

Check failure on line 46 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by PHPStan (locked, 8.3, ubuntu-latest)

Cannot access offset string on mixed.

Check failure on line 46 in src/DCB/CompositeProjection.php

View workflow job for this annotation

GitHub Actions / Static Analysis by PHPStan (locked, 8.3, ubuntu-latest)

Cannot access offset string on mixed.
}

return $state;
}
}
51 changes: 51 additions & 0 deletions src/DCB/DecisionModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\DCB;

use ArrayAccess;
use LogicException;
use OutOfBoundsException;
use Patchlevel\EventSourcing\Store\AppendCondition;

use function array_key_exists;

/**
* @experimental
* @psalm-immutable
* @implements ArrayAccess<string, mixed>
*/
final class DecisionModel implements ArrayAccess
{
/** @param array<string, mixed> $state */
public function __construct(
public readonly array $state,
public readonly AppendCondition $appendCondition,
) {
}

public function offsetExists(mixed $offset): bool
{
return array_key_exists($offset, $this->state);
}

public function offsetGet(mixed $offset): mixed
{
if (!$this->offsetExists($offset)) {
throw new OutOfBoundsException("Offset '$offset' does not exist in the state.");
}

return $this->state[$offset];
}

public function offsetSet(mixed $offset, mixed $value): void
{
throw new LogicException('State is immutable, cannot set value.');
}

public function offsetUnset(mixed $offset): void
{
throw new LogicException('State is immutable, cannot unset value.');
}
}
14 changes: 14 additions & 0 deletions src/DCB/DecisionModelBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\DCB;

/** @experimental */
interface DecisionModelBuilder
{
/** @param array<string, Projection> $projections */
public function build(
array $projections,
): DecisionModel;
}
18 changes: 18 additions & 0 deletions src/DCB/EventAppender.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\DCB;

use Patchlevel\EventSourcing\Store\AppendCondition;

/** @experimental */
interface EventAppender
{
/** @param iterable<object> $events */
public function append(
iterable $events,
AppendCondition|null $appendCondition = null,
string|null $streamName = null,
): void;
}
Loading
Loading