From 37d6e8912b7bc9359f89f578d3f8e652f88a192b Mon Sep 17 00:00:00 2001 From: David Badura Date: Sun, 2 Jan 2022 23:37:22 +0100 Subject: [PATCH 1/4] add benchmarks --- .github/workflows/benchmark.yml | 43 ++ Makefile | 4 + composer.json | 1 + composer.lock | 415 +++++++++++++++++- phpbench.json | 5 + .../BasicImplementation/Aggregate/Profile.php | 70 +++ .../Events/NameChanged.php | 28 ++ .../Events/ProfileCreated.php | 28 ++ .../Processor/SendEmailProcessor.php | 22 + .../Projection/ProfileProjection.php | 42 ++ .../BasicImplementation/SendEmailMock.php | 25 ++ .../BasicImplementation/data/.gitignore | 1 + tests/Benchmark/LoadEventsBench.php | 67 +++ .../LoadEventsWithSnapshotsBench.php | 70 +++ tests/Benchmark/WriteEventsBench.php | 87 ++++ .../Events/ProfileCreated.php | 11 +- 16 files changed, 914 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/benchmark.yml create mode 100644 phpbench.json create mode 100644 tests/Benchmark/BasicImplementation/Aggregate/Profile.php create mode 100644 tests/Benchmark/BasicImplementation/Events/NameChanged.php create mode 100644 tests/Benchmark/BasicImplementation/Events/ProfileCreated.php create mode 100644 tests/Benchmark/BasicImplementation/Processor/SendEmailProcessor.php create mode 100644 tests/Benchmark/BasicImplementation/Projection/ProfileProjection.php create mode 100644 tests/Benchmark/BasicImplementation/SendEmailMock.php create mode 100644 tests/Benchmark/BasicImplementation/data/.gitignore create mode 100644 tests/Benchmark/LoadEventsBench.php create mode 100644 tests/Benchmark/LoadEventsWithSnapshotsBench.php create mode 100644 tests/Benchmark/WriteEventsBench.php diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..7e7e0187 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,43 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Benchmark" + +on: + pull_request: + push: + branches: + - "[0-9]+.[0-9]+.x" + +jobs: + phpbench: + name: "Benchmark" + + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + dependencies: + - "locked" + php-version: + - "8.0" + operating-system: + - "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "pcov" + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1 + extensions: pdo_sqlite + + - uses: "ramsey/composer-install@v2" + with: + dependency-versions: ${{ matrix.dependencies }} + + - name: "PHPStan" + run: "vendor/bin/phpbench run tests/Benchmark --report=default" diff --git a/Makefile b/Makefile index e86b7a8b..d723226e 100644 --- a/Makefile +++ b/Makefile @@ -41,5 +41,9 @@ static: psalm phpstan phpcs-check test: phpunit ## run tests +.PHONY: benchmark +benchmark: vendor ## run benchmarks + vendor/bin/phpbench run tests/Benchmark --report=default + .PHONY: dev dev: static test ## run dev tools diff --git a/composer.json b/composer.json index 24a17aed..2fc1dfaa 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "infection/infection": "^0.21.5", "patchlevel/coding-standard": "^1.1.1", "patchlevel/event-sourcing-psalm-plugin": "^1.0.0", + "phpbench/phpbench": "^1.2", "phpspec/prophecy-phpunit": "^2.0.1", "phpstan/phpstan": "^1.2.0", "phpunit/phpunit": "^9.5.10", diff --git a/composer.lock b/composer.lock index 4e7c19ab..e395d892 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b2abbdd674bca52c21293f8ec086cfc4", + "content-hash": "0b9fd103c176edb084b82a646816976b", "packages": [ { "name": "doctrine/cache", @@ -985,6 +985,78 @@ }, "time": "2019-12-04T15:06:13+00:00" }, + { + "name": "doctrine/annotations", + "version": "1.13.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "5b668aef16090008790395c02c893b1ba13f7e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", + "reference": "5b668aef16090008790395c02c893b1ba13f7e08", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.2" + }, + "time": "2021-08-05T19:00:23+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.0", @@ -1054,6 +1126,86 @@ ], "time": "2020-11-10T18:47:58+00:00" }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, { "name": "doctrine/migrations", "version": "3.3.2", @@ -2273,6 +2425,196 @@ }, "time": "2021-02-23T14:00:09+00:00" }, + { + "name": "phpbench/container", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "4af6c2619296e95b72409fd6244f000276277047" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/4af6c2619296e95b72409fd6244f000276277047", + "reference": "4af6c2619296e95b72409fd6244f000276277047", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.0" + }, + "time": "2021-07-14T20:56:29+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/b013b717832ddbaadf2a40984b04bc66af9a7110", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.2||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.83", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.2" + }, + "time": "2021-09-24T15:26:07+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "31750c45791a3e0bb7311aebfd1a4ed6f8f3fc86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/31750c45791a3e0bb7311aebfd1a4ed6f8f3fc86", + "reference": "31750c45791a3e0bb7311aebfd1a4ed6f8f3fc86", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.13", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.1", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0", + "symfony/finder": "^4.2 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0", + "symfony/process": "^4.2 || ^5.0 || ^6.0", + "webmozart/path-util": "^2.3" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^0.8.1", + "phpspec/prophecy": "^1.12", + "phpstan/phpstan": "^0.12.7", + "phpunit/phpunit": "^8.5.8 || ^9.0", + "symfony/error-handler": "^5.2 || ^6.0", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2021-12-24T10:09:29+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -4844,6 +5186,75 @@ ], "time": "2021-12-08T15:13:44+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "b0fb78576487af19c500aaddb269fd36701d4847" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b0fb78576487af19c500aaddb269fd36701d4847", + "reference": "b0fb78576487af19c500aaddb269fd36701d4847", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T10:19:22+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.23.0", @@ -6125,5 +6536,5 @@ "platform-dev": { "ext-pdo_sqlite": "~8.0.0|~8.1.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.2.0" } diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000..d6abe57f --- /dev/null +++ b/phpbench.json @@ -0,0 +1,5 @@ +{ + "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.file_pattern": "*Bench.php" +} \ No newline at end of file diff --git a/tests/Benchmark/BasicImplementation/Aggregate/Profile.php b/tests/Benchmark/BasicImplementation/Aggregate/Profile.php new file mode 100644 index 00000000..00f98f82 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/Aggregate/Profile.php @@ -0,0 +1,70 @@ +id; + } + + public static function create(string $id, string $name): self + { + $self = new self(); + $self->record(ProfileCreated::raise($id, $name)); + + return $self; + } + + public function changeName(string $name): void + { + $this->record(NameChanged::raise($this->id, $name)); + } + + protected function applyProfileCreated(ProfileCreated $event): void + { + $this->id = $event->profileId(); + $this->name = $event->name(); + } + + protected function applyNameChanged(NameChanged $event): void + { + $this->name = $event->name(); + } + + /** + * @return array{id: string, name: string} + */ + protected function serialize(): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + ]; + } + + /** + * @param array{id: string, name: string} $payload + */ + protected static function deserialize(array $payload): static + { + $self = new static(); + $self->id = $payload['id']; + $self->name = $payload['name']; + + return $self; + } +} diff --git a/tests/Benchmark/BasicImplementation/Events/NameChanged.php b/tests/Benchmark/BasicImplementation/Events/NameChanged.php new file mode 100644 index 00000000..59c6e586 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/Events/NameChanged.php @@ -0,0 +1,28 @@ + + */ +final class NameChanged extends AggregateChanged +{ + public static function raise(string $id, string $name): static + { + return new static($id, ['name' => $name]); + } + + public function profileId(): string + { + return $this->aggregateId; + } + + public function name(): string + { + return $this->payload['name']; + } +} diff --git a/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php b/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php new file mode 100644 index 00000000..aac8c267 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php @@ -0,0 +1,28 @@ + + */ +final class ProfileCreated extends AggregateChanged +{ + public static function raise(string $id, string $name): static + { + return new static($id, ['id' => $id, 'name' => $name]); + } + + public function profileId(): string + { + return $this->aggregateId; + } + + public function name(): string + { + return $this->payload['name']; + } +} diff --git a/tests/Benchmark/BasicImplementation/Processor/SendEmailProcessor.php b/tests/Benchmark/BasicImplementation/Processor/SendEmailProcessor.php new file mode 100644 index 00000000..bbc0dc55 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/Processor/SendEmailProcessor.php @@ -0,0 +1,22 @@ +connection = $connection; + } + + public function handledEvents(): iterable + { + yield ProfileCreated::class => 'applyProfileCreated'; + } + + public function create(): void + { + $this->connection->executeStatement('CREATE TABLE IF NOT EXISTS projection_profile (id VARCHAR PRIMARY KEY);'); + } + + public function drop(): void + { + $this->connection->executeStatement('DROP TABLE IF EXISTS projection_profile;'); + } + + public function applyProfileCreated(ProfileCreated $profileCreated): void + { + $this->connection->executeStatement( + 'INSERT INTO projection_profile (`id`) VALUES(:id);', + ['id' => $profileCreated->profileId()] + ); + } +} diff --git a/tests/Benchmark/BasicImplementation/SendEmailMock.php b/tests/Benchmark/BasicImplementation/SendEmailMock.php new file mode 100644 index 00000000..9cfd13c9 --- /dev/null +++ b/tests/Benchmark/BasicImplementation/SendEmailMock.php @@ -0,0 +1,25 @@ + Driver::class, + 'path' => self::DB_PATH, + ]); + + $this->bus = new DefaultEventBus(); + + $this->store = new SingleTableStore( + $connection, + [Profile::class => 'profile'], + 'eventstore' + ); + + $repository = new DefaultRepository($this->store, $this->bus, Profile::class); + + // create tables + (new DoctrineSchemaManager())->create($this->store); + + $profile = Profile::create('1', 'Peter'); + + for ($i = 0; $i < 10_000; $i++) { + $profile->changeName('Peter'); + } + + $repository->save($profile); + } + + #[Bench\Revs(10)] + #[Bench\Iterations(2)] + public function benchLoadEvents(): void + { + $repository = new DefaultRepository($this->store, $this->bus, Profile::class); + + $repository->load('1'); + } +} \ No newline at end of file diff --git a/tests/Benchmark/LoadEventsWithSnapshotsBench.php b/tests/Benchmark/LoadEventsWithSnapshotsBench.php new file mode 100644 index 00000000..29def90c --- /dev/null +++ b/tests/Benchmark/LoadEventsWithSnapshotsBench.php @@ -0,0 +1,70 @@ + Driver::class, + 'path' => self::DB_PATH, + ]); + + $this->bus = new DefaultEventBus(); + + $this->store = new SingleTableStore( + $connection, + [Profile::class => 'profile'], + 'eventstore' + ); + + $this->snapshotStore = new InMemorySnapshotStore(); + $repository = new SnapshotRepository($this->store, $this->bus, Profile::class, $this->snapshotStore); + + // create tables + (new DoctrineSchemaManager())->create($this->store); + + $profile = Profile::create('1', 'Peter'); + + for ($i = 0; $i < 10_000; $i++) { + $profile->changeName('Peter'); + } + + $repository->save($profile); + } + + #[Bench\Revs(10)] + #[Bench\Iterations(2)] + public function benchLoadEvents(): void + { + $repository = new SnapshotRepository($this->store, $this->bus, Profile::class, $this->snapshotStore); + + $repository->load('1'); + } +} \ No newline at end of file diff --git a/tests/Benchmark/WriteEventsBench.php b/tests/Benchmark/WriteEventsBench.php new file mode 100644 index 00000000..8c198e05 --- /dev/null +++ b/tests/Benchmark/WriteEventsBench.php @@ -0,0 +1,87 @@ + Driver::class, + 'path' => self::DB_PATH, + ]); + + $profileProjection = new ProfileProjection($connection); + $projectionRepository = new DefaultProjectionRepository( + [$profileProjection] + ); + + $this->bus = new DefaultEventBus(); + $this->bus->addListener(new ProjectionListener($projectionRepository)); + $this->bus->addListener(new SendEmailProcessor()); + + $this->store = new SingleTableStore( + $connection, + [Profile::class => 'profile'], + 'eventstore' + ); + + $this->repository = new DefaultRepository($this->store, $this->bus, Profile::class); + + // create tables + $profileProjection->create(); + (new DoctrineSchemaManager())->create($this->store); + + $this->profile = Profile::create('1', 'Peter'); + $this->repository->save($this->profile); + } + + #[Bench\Revs(10)] + #[Bench\Iterations(2)] + public function benchSaveOneEvent(): void + { + $this->profile->changeName('Peter'); + $this->repository->save($this->profile); + } + + #[Bench\Revs(10)] + #[Bench\Iterations(2)] + public function benchSaveAfterThousandEvents(): void + { + for ($i = 0; $i < 1_000; $i++) { + $this->profile->changeName('Peter'); + } + + $this->repository->save($this->profile); + } +} \ No newline at end of file diff --git a/tests/Integration/BasicImplementation/Events/ProfileCreated.php b/tests/Integration/BasicImplementation/Events/ProfileCreated.php index 6102570e..7947f908 100644 --- a/tests/Integration/BasicImplementation/Events/ProfileCreated.php +++ b/tests/Integration/BasicImplementation/Events/ProfileCreated.php @@ -7,17 +7,22 @@ use Patchlevel\EventSourcing\Aggregate\AggregateChanged; /** - * @template-extends AggregateChanged + * @template-extends AggregateChanged */ final class ProfileCreated extends AggregateChanged { - public static function raise(string $id): static + public static function raise(string $id, string $name): static { - return new static($id, ['id' => $id]); + return new static($id, ['id' => $id, 'name' => $name]); } public function profileId(): string { return $this->aggregateId; } + + public function name(): string + { + return $this->payload['name']; + } } From c19d8f2c4d033ff8f48944d2ef3a135f5bb317f7 Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Tue, 4 Jan 2022 21:44:39 +0100 Subject: [PATCH 2/4] fix test according to the changes in profile --- .../BasicImplementation/Aggregate/Profile.php | 15 ++++++++++++--- .../BasicImplementation/BasicIntegrationTest.php | 16 ++++++++++++---- .../Projection/ProfileProjection.php | 9 ++++++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/Integration/BasicImplementation/Aggregate/Profile.php b/tests/Integration/BasicImplementation/Aggregate/Profile.php index 5cff0acd..e74c01e6 100644 --- a/tests/Integration/BasicImplementation/Aggregate/Profile.php +++ b/tests/Integration/BasicImplementation/Aggregate/Profile.php @@ -13,16 +13,17 @@ final class Profile extends SnapshotableAggregateRoot use NonStrictApplyMethod; private string $id; + private string $name; public function aggregateRootId(): string { return $this->id; } - public static function create(string $id): self + public static function create(string $id, string $name): self { $self = new self(); - $self->record(ProfileCreated::raise($id)); + $self->record(ProfileCreated::raise($id, $name)); return $self; } @@ -30,6 +31,7 @@ public static function create(string $id): self protected function applyProfileCreated(ProfileCreated $event): void { $this->id = $event->profileId(); + $this->name = $event->name(); } /** @@ -39,17 +41,24 @@ protected function serialize(): array { return [ 'id' => $this->id, + 'name' => $this->name, ]; } /** - * @param array{id: string} $payload + * @param array{id: string, name: string} $payload */ protected static function deserialize(array $payload): static { $self = new static(); $self->id = $payload['id']; + $self->name = $payload['name']; return $self; } + + public function name(): string + { + return $this->name; + } } diff --git a/tests/Integration/BasicImplementation/BasicIntegrationTest.php b/tests/Integration/BasicImplementation/BasicIntegrationTest.php index 1375ae5b..9d1eed9a 100644 --- a/tests/Integration/BasicImplementation/BasicIntegrationTest.php +++ b/tests/Integration/BasicImplementation/BasicIntegrationTest.php @@ -78,7 +78,7 @@ public function testSuccessful(): void (new DoctrineSchemaManager())->create($store); - $profile = Profile::create('1'); + $profile = Profile::create('1', 'John'); $repository->save($profile); $result = $this->connection->fetchAssociative('SELECT * FROM projection_profile WHERE id = "1"'); @@ -86,12 +86,14 @@ public function testSuccessful(): void self::assertIsArray($result); self::assertArrayHasKey('id', $result); self::assertEquals('1', $result['id']); + self::assertEquals('John', $result['name']); $repository = new DefaultRepository($store, $eventStream, Profile::class); $profile = $repository->load('1'); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); + self::assertEquals('John', $profile->name()); self::assertEquals(1, SendEmailMock::count()); } @@ -119,7 +121,7 @@ public function testWithSymfonySuccessful(): void $profileProjection->create(); (new DoctrineSchemaManager())->create($store); - $profile = Profile::create('1'); + $profile = Profile::create('1', 'John'); $repository->save($profile); $result = $this->connection->fetchAssociative('SELECT * FROM projection_profile WHERE id = "1"'); @@ -127,12 +129,14 @@ public function testWithSymfonySuccessful(): void self::assertIsArray($result); self::assertArrayHasKey('id', $result); self::assertEquals('1', $result['id']); + self::assertEquals('John', $result['name']); $repository = new DefaultRepository($store, $eventStream, Profile::class); $profile = $repository->load('1'); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); + self::assertEquals('John', $profile->name()); self::assertEquals(1, SendEmailMock::count()); } @@ -158,7 +162,7 @@ public function testMultiTableSuccessful(): void $profileProjection->create(); (new DoctrineSchemaManager())->create($store); - $profile = Profile::create('1'); + $profile = Profile::create('1', 'John'); $repository->save($profile); $result = $this->connection->fetchAssociative('SELECT * FROM projection_profile WHERE id = "1"'); @@ -166,12 +170,14 @@ public function testMultiTableSuccessful(): void self::assertIsArray($result); self::assertArrayHasKey('id', $result); self::assertEquals('1', $result['id']); + self::assertEquals('John', $result['name']); $repository = new DefaultRepository($store, $eventStream, Profile::class); $profile = $repository->load('1'); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); + self::assertEquals('John', $profile->name()); self::assertEquals(1, SendEmailMock::count()); } @@ -200,7 +206,7 @@ public function testSnapshot(): void $profileProjection->create(); (new DoctrineSchemaManager())->create($store); - $profile = Profile::create('1'); + $profile = Profile::create('1', 'John'); $repository->save($profile); $result = $this->connection->fetchAssociative('SELECT * FROM projection_profile WHERE id = "1"'); @@ -208,12 +214,14 @@ public function testSnapshot(): void self::assertIsArray($result); self::assertArrayHasKey('id', $result); self::assertEquals('1', $result['id']); + self::assertEquals('John', $result['name']); $repository = new SnapshotRepository($store, $eventStream, Profile::class, $snapshotStore); $profile = $repository->load('1'); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); + self::assertEquals('John', $profile->name()); self::assertEquals(1, SendEmailMock::count()); } } diff --git a/tests/Integration/BasicImplementation/Projection/ProfileProjection.php b/tests/Integration/BasicImplementation/Projection/ProfileProjection.php index 9a7baaba..d5202873 100644 --- a/tests/Integration/BasicImplementation/Projection/ProfileProjection.php +++ b/tests/Integration/BasicImplementation/Projection/ProfileProjection.php @@ -24,7 +24,7 @@ public function handledEvents(): iterable public function create(): void { - $this->connection->executeStatement('CREATE TABLE IF NOT EXISTS projection_profile (id VARCHAR PRIMARY KEY);'); + $this->connection->executeStatement('CREATE TABLE IF NOT EXISTS projection_profile (id VARCHAR PRIMARY KEY, name VARCHAR);'); } public function drop(): void @@ -35,8 +35,11 @@ public function drop(): void public function applyProfileCreated(ProfileCreated $profileCreated): void { $this->connection->executeStatement( - 'INSERT INTO projection_profile (`id`) VALUES(:id);', - ['id' => $profileCreated->profileId()] + 'INSERT INTO projection_profile (`id`, `name`) VALUES(:id, :name);', + [ + 'id' => $profileCreated->profileId(), + 'name' => $profileCreated->name(), + ] ); } } From eb4c0e524c54ddb4f5f76707dfa4dfc0eb3d1e25 Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Tue, 4 Jan 2022 21:45:12 +0100 Subject: [PATCH 3/4] fix cs --- tests/Benchmark/LoadEventsBench.php | 7 +++++-- tests/Benchmark/LoadEventsWithSnapshotsBench.php | 7 +++++-- tests/Benchmark/WriteEventsBench.php | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/Benchmark/LoadEventsBench.php b/tests/Benchmark/LoadEventsBench.php index 4d1f8c3c..28e16f8d 100644 --- a/tests/Benchmark/LoadEventsBench.php +++ b/tests/Benchmark/LoadEventsBench.php @@ -15,7 +15,10 @@ use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Aggregate\Profile; use PhpBench\Attributes as Bench; -#[Bench\BeforeMethods("setUp")] +use function file_exists; +use function unlink; + +#[Bench\BeforeMethods('setUp')] final class LoadEventsBench { private const DB_PATH = __DIR__ . '/BasicImplementation/data/db.sqlite3'; @@ -64,4 +67,4 @@ public function benchLoadEvents(): void $repository->load('1'); } -} \ No newline at end of file +} diff --git a/tests/Benchmark/LoadEventsWithSnapshotsBench.php b/tests/Benchmark/LoadEventsWithSnapshotsBench.php index 29def90c..51a856de 100644 --- a/tests/Benchmark/LoadEventsWithSnapshotsBench.php +++ b/tests/Benchmark/LoadEventsWithSnapshotsBench.php @@ -16,7 +16,10 @@ use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Aggregate\Profile; use PhpBench\Attributes as Bench; -#[Bench\BeforeMethods("setUp")] +use function file_exists; +use function unlink; + +#[Bench\BeforeMethods('setUp')] final class LoadEventsWithSnapshotsBench { private const DB_PATH = __DIR__ . '/BasicImplementation/data/db.sqlite3'; @@ -67,4 +70,4 @@ public function benchLoadEvents(): void $repository->load('1'); } -} \ No newline at end of file +} diff --git a/tests/Benchmark/WriteEventsBench.php b/tests/Benchmark/WriteEventsBench.php index 8c198e05..0362b3a0 100644 --- a/tests/Benchmark/WriteEventsBench.php +++ b/tests/Benchmark/WriteEventsBench.php @@ -20,7 +20,10 @@ use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Projection\ProfileProjection; use PhpBench\Attributes as Bench; -#[Bench\BeforeMethods("setUp")] +use function file_exists; +use function unlink; + +#[Bench\BeforeMethods('setUp')] final class WriteEventsBench { private const DB_PATH = __DIR__ . '/BasicImplementation/data/db.sqlite3'; @@ -84,4 +87,4 @@ public function benchSaveAfterThousandEvents(): void $this->repository->save($this->profile); } -} \ No newline at end of file +} From 73ede63b76b8f6492943d4dabe72f743331e84b7 Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Tue, 4 Jan 2022 22:29:31 +0100 Subject: [PATCH 4/4] fix psalm issues, update baseline --- baseline.xml | 29 +++++++++++++++++-- psalm.xml | 1 + .../BasicImplementation/Aggregate/Profile.php | 5 ++++ .../Projection/ProfileProjection.php | 9 ++++-- .../BasicImplementation/Aggregate/Profile.php | 2 +- .../BasicIntegrationTest.php | 4 +++ 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/baseline.xml b/baseline.xml index eac6f8fe..c7f701d6 100644 --- a/baseline.xml +++ b/baseline.xml @@ -15,6 +15,32 @@ $bucket->event() instanceof $class + + + $payload + + + + + $bus + $store + + + + + $bus + $snapshotStore + $store + + + + + $bus + $profile + $repository + $store + + $payload @@ -29,9 +55,6 @@ - - SnapshotableAggregateRoot - $payload diff --git a/psalm.xml b/psalm.xml index 635d4796..3c3ffe1e 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,6 +14,7 @@ + diff --git a/tests/Benchmark/BasicImplementation/Aggregate/Profile.php b/tests/Benchmark/BasicImplementation/Aggregate/Profile.php index 00f98f82..5e57872f 100644 --- a/tests/Benchmark/BasicImplementation/Aggregate/Profile.php +++ b/tests/Benchmark/BasicImplementation/Aggregate/Profile.php @@ -67,4 +67,9 @@ protected static function deserialize(array $payload): static return $self; } + + public function name(): string + { + return $this->name; + } } diff --git a/tests/Benchmark/BasicImplementation/Projection/ProfileProjection.php b/tests/Benchmark/BasicImplementation/Projection/ProfileProjection.php index fef88c0c..c82cfd05 100644 --- a/tests/Benchmark/BasicImplementation/Projection/ProfileProjection.php +++ b/tests/Benchmark/BasicImplementation/Projection/ProfileProjection.php @@ -24,7 +24,7 @@ public function handledEvents(): iterable public function create(): void { - $this->connection->executeStatement('CREATE TABLE IF NOT EXISTS projection_profile (id VARCHAR PRIMARY KEY);'); + $this->connection->executeStatement('CREATE TABLE IF NOT EXISTS projection_profile (id VARCHAR PRIMARY KEY, name VARCHAR);'); } public function drop(): void @@ -35,8 +35,11 @@ public function drop(): void public function applyProfileCreated(ProfileCreated $profileCreated): void { $this->connection->executeStatement( - 'INSERT INTO projection_profile (`id`) VALUES(:id);', - ['id' => $profileCreated->profileId()] + 'INSERT INTO projection_profile (`id`, `name`) VALUES(:id, :name);', + [ + 'id' => $profileCreated->profileId(), + 'name' => $profileCreated->name(), + ] ); } } diff --git a/tests/Integration/BasicImplementation/Aggregate/Profile.php b/tests/Integration/BasicImplementation/Aggregate/Profile.php index e74c01e6..79db7b4a 100644 --- a/tests/Integration/BasicImplementation/Aggregate/Profile.php +++ b/tests/Integration/BasicImplementation/Aggregate/Profile.php @@ -35,7 +35,7 @@ protected function applyProfileCreated(ProfileCreated $event): void } /** - * @return array{id: string} + * @return array{id: string, name: string} */ protected function serialize(): array { diff --git a/tests/Integration/BasicImplementation/BasicIntegrationTest.php b/tests/Integration/BasicImplementation/BasicIntegrationTest.php index 9d1eed9a..dfcd1794 100644 --- a/tests/Integration/BasicImplementation/BasicIntegrationTest.php +++ b/tests/Integration/BasicImplementation/BasicIntegrationTest.php @@ -91,6 +91,7 @@ public function testSuccessful(): void $repository = new DefaultRepository($store, $eventStream, Profile::class); $profile = $repository->load('1'); + self::assertInstanceOf(Profile::class, $profile); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); self::assertEquals('John', $profile->name()); @@ -134,6 +135,7 @@ public function testWithSymfonySuccessful(): void $repository = new DefaultRepository($store, $eventStream, Profile::class); $profile = $repository->load('1'); + self::assertInstanceOf(Profile::class, $profile); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); self::assertEquals('John', $profile->name()); @@ -175,6 +177,7 @@ public function testMultiTableSuccessful(): void $repository = new DefaultRepository($store, $eventStream, Profile::class); $profile = $repository->load('1'); + self::assertInstanceOf(Profile::class, $profile); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); self::assertEquals('John', $profile->name()); @@ -219,6 +222,7 @@ public function testSnapshot(): void $repository = new SnapshotRepository($store, $eventStream, Profile::class, $snapshotStore); $profile = $repository->load('1'); + self::assertInstanceOf(Profile::class, $profile); self::assertEquals('1', $profile->aggregateRootId()); self::assertEquals(1, $profile->playhead()); self::assertEquals('John', $profile->name());