diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfcd0d3..0aa8b46 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,11 +8,12 @@ on: jobs: test: + name: Test on PHP ${{ matrix.php-version }} runs-on: ubuntu-latest strategy: matrix: - php-version: [ '8.1', '8.2', '8.3' ] + php-version: [ '8.2', '8.3', '8.4' ] steps: - name: Checkout source code @@ -28,7 +29,7 @@ jobs: tools: composer:v2 - name: Cache dependencies - uses: actions/cache@v2 + uses: actions/cache@v4.3.0 with: path: ~/.composer/cache key: php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }} @@ -37,9 +38,15 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate - - name: Install dependencies + - name: Install Dependencies if: steps.composer-cache.outputs.cache-hit != 'true' - run: composer install --prefer-dist --no-progress --no-suggest + run: composer install --prefer-dist --no-progress + + - name: Check Code Style + run: composer pint-test - name: Execute Static Code analysis - run: composer analyse \ No newline at end of file + run: composer analyse + + - name: Execute Unit, Integration and Acceptance Tests + run: composer test diff --git a/.gitignore b/.gitignore index 43cae52..9f4ffa7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ composer.phar /vendor/ .idea/ -/infrastructure +/infrastructure/ +/coverage/ *[N|n]o[G|g]it* -*coverage* +coverage.xml +test.xml .phpunit.result.cache composer.lock +wiki/Home.md +/.phpunit.cache +.phpactor.json +/.serena/ \ No newline at end of file diff --git a/README.md b/README.md index c54b39f..aa40d95 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,62 @@ Common interfaces for PHP Complex Heart SDK. -## Domain Modeling - -- Aggregate -- Entity -- ValueObject -- Identity - -## Service Bus - -- ServiceBus -- Command -- CommandBus -- CommandHandler -- Event -- EventBus -- EventHandler -- Query -- QueryBus -- QueryHandler -- QueryResponse +## Domain Layer + +### Model +Core building blocks for domain-driven design: +- **Aggregate** - Root entity with domain event publishing +- **Entity** - Domain object with unique identity +- **ValueObject** - Immutable domain value with equality +- **Identifier** - Unique identifier representation + +### Events +Domain event interfaces following ISP (Interface Segregation Principle): +- **Event** - Base domain event (eventId, eventName, payload, occurredOn) +- **Traceable** - Distributed tracing (correlationId, causationId) +- **Sourceable** - Event sourcing (aggregateId, aggregateType, eventVersion) +- **EventBus** - Publishes domain events + +## Application Layer + +### Command +Write operations (CQRS): +- **Command** - Marker interface for state-changing operations +- **CommandBus** - Dispatches commands to handlers +- **CommandHandler** - Executes commands + +### Query +Read operations (CQRS): +- **Query** - Marker interface for data retrieval +- **QueryResponse** - Marker interface for query results +- **QueryBus** - Routes queries to handlers +- **QueryHandler** - Executes queries and returns responses + +### Handler +Event handlers: +- **EventHandler** - Reacts to domain events + +### Service Bus +Unified message bus facade: +- **ServiceBus** - Provides access to CommandBus, QueryBus, and EventBus + +## Architecture + +This library follows Clean Architecture principles with explicit layer separation: +- **Domain → Application** - Domain layer is independent, Application depends on Domain +- **Layer-Explicit Namespaces** - Clear architectural boundaries in namespace structure +- **Interface Segregation** - Compose only needed capabilities (e.g., Event + Traceable + Sourceable) + +### Architecture Testing + +The project includes automated architecture tests using Pest PHP: + +```bash +composer test +``` + +Tests enforce: +- Domain layer independence (no Application dependencies) +- Correct interface placement and usage +- Clean Architecture dependency rules +- PHP and security best practices (via arch presets) diff --git a/composer.json b/composer.json index 7de492c..6c6e28d 100644 --- a/composer.json +++ b/composer.json @@ -11,11 +11,14 @@ ], "minimum-stability": "stable", "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "phpstan/phpstan": "^1.0", - "phpstan/extension-installer": "^1.3" + "phpstan/extension-installer": "^1.3", + "laravel/pint": "^1.25", + "pestphp/pest": "^3.8.4", + "pestphp/pest-plugin-arch": "^3.1.1" }, "autoload": { "psr-4": { @@ -23,10 +26,14 @@ } }, "scripts": { - "analyse": "vendor/bin/phpstan analyse src --no-progress --level=9" + "test": "vendor/bin/pest --configuration=phpunit.xml --coverage --coverage-clover=coverage.xml --log-junit=test.xml", + "analyse": "vendor/bin/phpstan analyse src --no-progress --memory-limit=4G --level=9", + "pint-test": "vendor/bin/pint --preset=psr12 --test", + "pint": "vendor/bin/pint --preset=psr12" }, "config": { "allow-plugins": { + "pestphp/pest-plugin": true, "phpstan/extension-installer": true } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e266abd --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + + + + + + ./tests + + + + + + ./src + + + diff --git a/src/Application/Command/Command.php b/src/Application/Command/Command.php new file mode 100644 index 0000000..658b6ad --- /dev/null +++ b/src/Application/Command/Command.php @@ -0,0 +1,17 @@ + + */ +interface Command +{ +} diff --git a/src/Application/Command/CommandBus.php b/src/Application/Command/CommandBus.php new file mode 100644 index 0000000..5ba8995 --- /dev/null +++ b/src/Application/Command/CommandBus.php @@ -0,0 +1,18 @@ + + */ +interface CommandBus +{ + /** + * Dispatch the given command. + */ + public function dispatch(Command $command): void; +} diff --git a/src/Domain/Contracts/ServiceBus/CommandHandler.php b/src/Application/Handler/CommandHandler.php similarity index 50% rename from src/Domain/Contracts/ServiceBus/CommandHandler.php rename to src/Application/Handler/CommandHandler.php index ffabd39..f822032 100644 --- a/src/Domain/Contracts/ServiceBus/CommandHandler.php +++ b/src/Application/Handler/CommandHandler.php @@ -2,20 +2,19 @@ declare(strict_types=1); -namespace ComplexHeart\Domain\Contracts\ServiceBus; +namespace ComplexHeart\Application\Handler; + +use ComplexHeart\Application\Command\Command; /** * Interface CommandHandler * - * @author Unay Santisteban - * @package ComplexHeart\Domain\Contracts\ServiceBus + * @author Unay Santisteban */ interface CommandHandler { /** * Handle the command execution. - * - * @param Command $command */ public function __invoke(Command $command): void; } diff --git a/src/Domain/Contracts/ServiceBus/EventHandler.php b/src/Application/Handler/EventHandler.php similarity index 50% rename from src/Domain/Contracts/ServiceBus/EventHandler.php rename to src/Application/Handler/EventHandler.php index 270fe51..a681e32 100644 --- a/src/Domain/Contracts/ServiceBus/EventHandler.php +++ b/src/Application/Handler/EventHandler.php @@ -2,20 +2,19 @@ declare(strict_types=1); -namespace ComplexHeart\Domain\Contracts\ServiceBus; +namespace ComplexHeart\Application\Handler; + +use ComplexHeart\Domain\Events\Event; /** * Interface EventHandler * - * @author Unay Santisteban - * @package ComplexHeart\Domain\Contracts\ServiceBus + * @author Unay Santisteban */ interface EventHandler { /** * Handle the event execution. - * - * @param Event $event */ public function __invoke(Event $event): void; } diff --git a/src/Application/Handler/QueryHandler.php b/src/Application/Handler/QueryHandler.php new file mode 100644 index 0000000..b807f25 --- /dev/null +++ b/src/Application/Handler/QueryHandler.php @@ -0,0 +1,21 @@ + + */ +interface QueryHandler +{ + /** + * Handle the query execution. + */ + public function __invoke(Query $query): QueryResponse; +} diff --git a/src/Application/Query/Query.php b/src/Application/Query/Query.php new file mode 100644 index 0000000..047455f --- /dev/null +++ b/src/Application/Query/Query.php @@ -0,0 +1,17 @@ + + */ +interface Query +{ +} diff --git a/src/Application/Query/QueryBus.php b/src/Application/Query/QueryBus.php new file mode 100644 index 0000000..a33f445 --- /dev/null +++ b/src/Application/Query/QueryBus.php @@ -0,0 +1,18 @@ + + */ +interface QueryBus +{ + /** + * Ask the given query. + */ + public function ask(Query $query): QueryResponse; +} diff --git a/src/Application/Query/QueryResponse.php b/src/Application/Query/QueryResponse.php new file mode 100644 index 0000000..dad9c2f --- /dev/null +++ b/src/Application/Query/QueryResponse.php @@ -0,0 +1,17 @@ + + */ +interface QueryResponse +{ +} diff --git a/src/Domain/Contracts/ServiceBus/ServiceBus.php b/src/Application/ServiceBus.php similarity index 61% rename from src/Domain/Contracts/ServiceBus/ServiceBus.php rename to src/Application/ServiceBus.php index 21101f6..2da19bd 100644 --- a/src/Domain/Contracts/ServiceBus/ServiceBus.php +++ b/src/Application/ServiceBus.php @@ -2,34 +2,31 @@ declare(strict_types=1); -namespace ComplexHeart\Domain\Contracts\ServiceBus; +namespace ComplexHeart\Application; + +use ComplexHeart\Application\Command\CommandBus; +use ComplexHeart\Application\Query\QueryBus; +use ComplexHeart\Domain\Events\EventBus; /** * Interface ServiceBus * - * @author Unay Santisteban - * @package ComplexHeart\Domain\Contracts\ServiceBus + * @author Unay Santisteban */ interface ServiceBus { /** * Return the Command Bus implementation. - * - * @return CommandBus */ public function commandBus(): CommandBus; /** * Return the Query Bus implementation. - * - * @return QueryBus */ public function queryBus(): QueryBus; /** * Return the Event Bus implementation. - * - * @return EventBus */ public function eventBus(): EventBus; } diff --git a/src/Domain/Contracts/Model/Entity.php b/src/Domain/Contracts/Model/Entity.php deleted file mode 100644 index 5521e5f..0000000 --- a/src/Domain/Contracts/Model/Entity.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\Model - */ -interface Entity -{ - /** - * Return the Identifier instance. - * - * @return Identifier - */ - public function id(): Identifier; -} diff --git a/src/Domain/Contracts/ServiceBus/Command.php b/src/Domain/Contracts/ServiceBus/Command.php deleted file mode 100644 index 2b469eb..0000000 --- a/src/Domain/Contracts/ServiceBus/Command.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface Command -{ - -} diff --git a/src/Domain/Contracts/ServiceBus/CommandBus.php b/src/Domain/Contracts/ServiceBus/CommandBus.php deleted file mode 100644 index 1c88b30..0000000 --- a/src/Domain/Contracts/ServiceBus/CommandBus.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface CommandBus -{ - /** - * Dispatch the given command. - * - * @param Command $command - */ - public function dispatch(Command $command): void; -} diff --git a/src/Domain/Contracts/ServiceBus/Event.php b/src/Domain/Contracts/ServiceBus/Event.php deleted file mode 100644 index 8c32bda..0000000 --- a/src/Domain/Contracts/ServiceBus/Event.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface Event -{ - /** - * The unique id for the current domain event (UUID). - * - * @return string - */ - public function eventId(): string; - - /** - * The unique domain event name, commonly is a dotted string. - * - * i.e: this.object.event.name - * - * @return string - */ - public function eventName(): string; - - /** - * The aggregate id that has registered the domain event (UUID). - * - * @return string - */ - public function aggregateId(): string; - - /** - * Returns the event payload. - * - * @return array - */ - public function payload(): array; - - /** - * The timestamp on when the domain event is registered in ISO-8601. - * - * i.e: 2005-08-15T15:52:01+0000 - * - * @return string - */ - public function occurredOn(): string; -} diff --git a/src/Domain/Contracts/ServiceBus/EventBus.php b/src/Domain/Contracts/ServiceBus/EventBus.php deleted file mode 100644 index f8a8608..0000000 --- a/src/Domain/Contracts/ServiceBus/EventBus.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface EventBus -{ - /** - * Publish event list. - * - * @param Event ...$events - */ - public function publish(Event ...$events): void; -} diff --git a/src/Domain/Contracts/ServiceBus/Query.php b/src/Domain/Contracts/ServiceBus/Query.php deleted file mode 100644 index 9c2b411..0000000 --- a/src/Domain/Contracts/ServiceBus/Query.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface Query -{ - -} diff --git a/src/Domain/Contracts/ServiceBus/QueryBus.php b/src/Domain/Contracts/ServiceBus/QueryBus.php deleted file mode 100644 index 2420f28..0000000 --- a/src/Domain/Contracts/ServiceBus/QueryBus.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface QueryBus -{ - /** - * Ask the given query. - * - * @param Query $query - * - * @return QueryResponse - */ - public function ask(Query $query): QueryResponse; -} diff --git a/src/Domain/Contracts/ServiceBus/QueryHandler.php b/src/Domain/Contracts/ServiceBus/QueryHandler.php deleted file mode 100644 index f4765c0..0000000 --- a/src/Domain/Contracts/ServiceBus/QueryHandler.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface QueryHandler -{ - /** - * Handle the query execution. - * - * @param Query $query - * - * @return QueryResponse - */ - public function __invoke(Query $query): QueryResponse; -} diff --git a/src/Domain/Contracts/ServiceBus/QueryResponse.php b/src/Domain/Contracts/ServiceBus/QueryResponse.php deleted file mode 100644 index dc11996..0000000 --- a/src/Domain/Contracts/ServiceBus/QueryResponse.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @package ComplexHeart\Domain\Contracts\ServiceBus - */ -interface QueryResponse -{ - -} diff --git a/src/Domain/Events/Event.php b/src/Domain/Events/Event.php new file mode 100644 index 0000000..e8fbcb2 --- /dev/null +++ b/src/Domain/Events/Event.php @@ -0,0 +1,41 @@ + + */ +interface Event +{ + /** + * The unique id for the current domain event (UUID). + */ + public function eventId(): string; + + /** + * The unique domain event name, commonly is a dotted string. + * + * Examples: order.placed, user.registered, payment.processed + */ + public function eventName(): string; + + /** + * Returns the event payload. + * + * @return array + */ + public function payload(): array; + + /** + * The timestamp when the domain event occurred in ISO-8601. + * + * Example: 2005-08-15T15:52:01+0000 + */ + public function occurredOn(): string; +} diff --git a/src/Domain/Events/EventBus.php b/src/Domain/Events/EventBus.php new file mode 100644 index 0000000..f4d9962 --- /dev/null +++ b/src/Domain/Events/EventBus.php @@ -0,0 +1,18 @@ + + */ +interface EventBus +{ + /** + * Publish event list. + */ + public function publish(Event ...$events): void; +} diff --git a/src/Domain/Events/Sourceable.php b/src/Domain/Events/Sourceable.php new file mode 100644 index 0000000..e9d6a1c --- /dev/null +++ b/src/Domain/Events/Sourceable.php @@ -0,0 +1,95 @@ + + */ +interface Sourceable +{ + /** + * The unique identifier of the aggregate that emitted this event. + * + * This method provides explicit aggregate identification rather than + * relying on convention-based extraction from the payload, offering: + * - Type safety (guaranteed string, not array lookup) + * - IDE support (autocomplete, type hints, refactoring) + * - Contract enforcement at compile time + * - Clear event store stream naming + * + * Event stores typically use this to build stream names: + * Stream name = aggregateType() . "-" . aggregateId() + * + * Example: + * For an Order aggregate with ID "order-123", this returns "order-123" + * Stream name becomes: "Order-order-123" + * + * @return string The aggregate identifier (UUID, integer, or custom ID) + */ + public function aggregateId(): string; + + /** + * The type of aggregate that emitted this event. + * + * Identifies which aggregate class/type this event belongs to. + * Used by event stores for stream organization and aggregate reconstruction. + * + * Typical values (choose what works for your event store): + * - Fully qualified class name: "App\\Domain\\Order\\Order" + * - Short class name: "Order" + * - Type identifier: "order" + * - Custom identifier: "sales.order" + * + * Consistency is key - use the same format across all events. + * + * @return string The aggregate type identifier + */ + public function aggregateType(): string; + + /** + * The schema version of this event. + * + * Critical for event sourcing systems where events are stored permanently + * and aggregates must be rebuilt from historical events over time. + * + * Versioning strategies: + * - Semantic versioning: "1.0.0", "1.1.0", "2.0.0" + * - Simple incrementing: "1", "2", "3" + * - Date-based: "2024-01-15", "2025-10-14" + * + * When to increment the version: + * - Adding new fields: minor version (1.0 → 1.1) + * - Removing fields: major version (1.0 → 2.0) + * - Changing field types: major version (1.0 → 2.0) + * - Renaming fields: major version (1.0 → 2.0) + * + * Event stores use this for: + * - Upcasting old events to new schema + * - Maintaining multiple versions simultaneously + * - Migration strategies and backward compatibility + * + * Example evolution: + * - OrderPlaced v1.0: {orderId, total} + * - OrderPlaced v1.1: {orderId, total, customerId} (added field) + * - OrderPlaced v2.0: {orderId, total, customerId, items[]} (added array) + * + * @return string Version identifier (semantic, numeric, or date-based) + */ + public function eventVersion(): string; +} diff --git a/src/Domain/Events/Traceable.php b/src/Domain/Events/Traceable.php new file mode 100644 index 0000000..966d279 --- /dev/null +++ b/src/Domain/Events/Traceable.php @@ -0,0 +1,67 @@ + + */ +interface Traceable +{ + /** + * The correlation ID that traces the entire request/workflow. + * + * The same correlation ID flows through all services and events + * in a single business transaction, enabling end-to-end tracing. + * + * Example flow: + * - HTTP Request (correlationId: abc-123) + * - OrderPlaced event (correlationId: abc-123) + * - PaymentRequested event (correlationId: abc-123) + * - PaymentProcessed event (correlationId: abc-123) + * + * Use cases: + * - Distributed tracing across microservices + * - Debugging async workflows + * - APM tools integration + * - Request flow visualization + * + * @return string UUID or custom trace ID + */ + public function correlationId(): string; + + /** + * The causation ID - the immediate parent event that caused this event. + * + * Forms a causal chain showing parent-child relationships between events. + * Each event's causationId points to the eventId of its immediate parent. + * + * Example chain: + * - OrderPlaced (eventId: ev-001, causationId: null) + * ↓ + * - PaymentRequested (eventId: ev-002, causationId: ev-001) + * ↓ + * - PaymentProcessed (eventId: ev-003, causationId: ev-002) + * + * Use cases: + * - Event chain reconstruction + * - Root cause analysis (trace back to origin) + * - Understanding event dependencies + * - Building event graphs and visualizations + * + * @return string Event ID of the parent event that triggered this event + */ + public function causationId(): string; +} diff --git a/src/Domain/Contracts/Model/Aggregate.php b/src/Domain/Model/Aggregate.php similarity index 58% rename from src/Domain/Contracts/Model/Aggregate.php rename to src/Domain/Model/Aggregate.php index b93fca9..9d496d7 100644 --- a/src/Domain/Contracts/Model/Aggregate.php +++ b/src/Domain/Model/Aggregate.php @@ -2,15 +2,14 @@ declare(strict_types=1); -namespace ComplexHeart\Domain\Contracts\Model; +namespace ComplexHeart\Domain\Model; -use ComplexHeart\Domain\Contracts\ServiceBus\EventBus; +use ComplexHeart\Domain\Events\EventBus; /** * Interface Aggregate * - * @author Unay Santisteban - * @package ComplexHeart\Domain\Contracts\Model + * @author Unay Santisteban */ interface Aggregate extends Entity { @@ -20,9 +19,6 @@ interface Aggregate extends Entity * $aggregate = new Aggregate(); * // do things and generate events * $aggregate->publishDomainEvents($eventBus); - * - * @param EventBus $eventBus - * @return void */ public function publishDomainEvents(EventBus $eventBus): void; } diff --git a/src/Domain/Model/Entity.php b/src/Domain/Model/Entity.php new file mode 100644 index 0000000..38f57b8 --- /dev/null +++ b/src/Domain/Model/Entity.php @@ -0,0 +1,18 @@ + + */ +interface Entity +{ + /** + * Return the Identifier instance. + */ + public function id(): Identifier; +} diff --git a/src/Domain/Contracts/Model/Identifier.php b/src/Domain/Model/Identifier.php similarity index 63% rename from src/Domain/Contracts/Model/Identifier.php rename to src/Domain/Model/Identifier.php index 46db6ab..edffc22 100644 --- a/src/Domain/Contracts/Model/Identifier.php +++ b/src/Domain/Model/Identifier.php @@ -2,13 +2,12 @@ declare(strict_types=1); -namespace ComplexHeart\Domain\Contracts\Model; +namespace ComplexHeart\Domain\Model; /** * Interface Identifier * - * @author Unay Santisteban - * @package ComplexHeart\Domain\Contracts\Model + * @author Unay Santisteban */ interface Identifier { @@ -16,15 +15,11 @@ interface Identifier * Return the id value. * $id->value() // "7a79bffb-4b4e-4d82-932e-c1524723622b" * $id->value() // "459" - * - * @return string */ public function value(): string; /** * Represents the id as string. - * - * @return string */ public function __toString(): string; } diff --git a/src/Domain/Contracts/Model/ValueObject.php b/src/Domain/Model/ValueObject.php similarity index 61% rename from src/Domain/Contracts/Model/ValueObject.php rename to src/Domain/Model/ValueObject.php index 5c12815..e6d1abe 100644 --- a/src/Domain/Contracts/Model/ValueObject.php +++ b/src/Domain/Model/ValueObject.php @@ -2,13 +2,12 @@ declare(strict_types=1); -namespace ComplexHeart\Domain\Contracts\Model; +namespace ComplexHeart\Domain\Model; /** * Interface ValueObject * - * @author Unay Santisteban - * @package ComplexHeart\Domain\Contracts\Model + * @author Unay Santisteban */ interface ValueObject { @@ -21,10 +20,6 @@ public function values(): array; /** * Compare $this object with $other object. - * - * @param object $other - * - * @return bool */ public function equals(object $other): bool; } diff --git a/tests/ArchTest.php b/tests/ArchTest.php new file mode 100644 index 0000000..14923c4 --- /dev/null +++ b/tests/ArchTest.php @@ -0,0 +1,65 @@ +expect('ComplexHeart\Domain') + ->not->toUse('ComplexHeart\Application'); + +arch('Application layer can depend on Domain layer') + ->expect('ComplexHeart\Application') + ->toUse('ComplexHeart\Domain'); + +arch('Domain Model interfaces are correctly placed') + ->expect('ComplexHeart\Domain\Model') + ->toBeInterfaces() + ->toOnlyBeUsedIn([ + 'ComplexHeart\Domain\Model', + 'ComplexHeart\Domain\Events', + 'ComplexHeart\Application', + ]); + +arch('Domain Event interfaces are correctly placed') + ->expect('ComplexHeart\Domain\Events') + ->toBeInterfaces() + ->toOnlyBeUsedIn([ + 'ComplexHeart\Domain', + 'ComplexHeart\Application', + ]); + +arch('Application Command interfaces are correctly placed') + ->expect('ComplexHeart\Application\Command') + ->toBeInterfaces() + ->toOnlyBeUsedIn([ + 'ComplexHeart\Application', + ]); + +arch('Application Query interfaces are correctly placed') + ->expect('ComplexHeart\Application\Query') + ->toBeInterfaces() + ->toOnlyBeUsedIn([ + 'ComplexHeart\Application', + ]); + +arch('Application Handler interfaces are correctly placed') + ->expect('ComplexHeart\Application\Handler') + ->toBeInterfaces() + ->toOnlyBeUsedIn([ + 'ComplexHeart\Application', + ]); + +arch('Application Messaging interfaces are correctly placed') + ->expect('ComplexHeart\Application\Messaging') + ->toBeInterfaces() + ->toOnlyBeUsedIn([ + 'ComplexHeart\Application', + 'ComplexHeart\Domain\Model', // Aggregate uses EventBus + ]); + +arch('all interfaces are suffixed correctly') + ->expect('ComplexHeart') + ->classes() + ->toBeInterfaces(); + +arch()->preset()->php(); +arch()->preset()->security(); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..174d7fd --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,3 @@ +