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 @@
+