A set of contracts and interfaces to use in different packages
MIT License
List of interfaces use as contract in other packages or DD projects
This contract includes some strong typings, object relation and psalm validation.
Require php >= 7.2.*
and php >= 8.0
Release name | Branch name | Php Version |
---|---|---|
1.x | release/1.X | php >= 7.2 & php <= 8.0 |
2.x | master | php >= 8.0 |
Domain contract are part of DDD implementation suggestion, it's not required and is not linked to any frameworks.
Identity are used to define a unique identifier for an Entity or a RootAggregate.
Identity must be:
new
==> Generate (if possible) a new Identity object with a random value (like UUIDs)from
==> Instantiate Identity from an existing valueSee detailed implementation proposal: jeckel-lab/identity-contract
Entity: main Entity contract
Entity must have an Id implementing the Identity
interface.
Don't forget to use @psalm templates
/**
* DiverId is using an `int` as unique identifier
* @implements Identity<int>
*/
final class DriverId implements Identity
{
}
/**
* Now Driver can use a DriverId as an identifier
* @implements Entity<DriverId>
*/
class Driver implements Entity
{
public function __construct(private DriverId $id)
{
}
/**
* @return DriverId
*/
public function getId(): Identity
{
return $id;
}
}
Event are notification about what happened during a use case.
Event must be:
Entities and root aggregates handle domain events. To facilitate this behaviour, you can use this interface and trait:
This interface defines two methods:
/**
* @param Event ...$events
* @return static
*/
public function addDomainEvent(Event ...$events): static;
/**
* @return list<Event>
*/
public function popEvents(): array;
addDomainEvent
allow you to register new event occurred during a Use Case.popEvent
will empty the entity's event list at the end of a use case to dispatch them into an Event Dispatcher.Just use the interface and trait into your entity:
class MyEntity implement DomainEventAwareInterface
{
use DomainEventAwareTrait;
/**
* Example of a use case that add an event to the queue
* @return self
*/
public function activateEntity(): self
{
$this->activated = true;
$this->addDomainEvent(new EntityActivated($this->id));
return $this;
}
//...
}
And if you use the CommandBus pattern, then you can add events to the response easily:
new CommandResponse(events: $entity->popEvents());
Using ValueObject
to embed a value (or group of value for complex types) as an object allow you:
Speed
can not be mixed with any random float)Speed
is always a positive value, is lower than a reasonable value, etc.)Value object must be defined as:
from
method as a factoryfrom
should return the same instanceThink about implementing it like this:
final class Speed implements ValueObject, ValueObjectFactory
{
private static $instances = [];
private function __constructor(private float $speed)
{
}
/**
* @param mixed $value
* @return static
* @throws InvalidArgumentException
*/
public static function from(mixed $speedValue): static
{
if (! self::$instances[$speedValue]) {
if ($speedValue < 0) {
throw new InvalidArgumentException('Speed needs to be positive');
}
self::$instances[$speedValue] = new self($speedValue);
}
self::$instances[$speedValue]
}
// implements other methods
}
// And now
$speed1 = Speed::from(85.2);
$speed2 = Speed::from(85.2);
$speed1 === $speed2; // is true
To be completed
To be completed
See detailed implementation proposal: jeckel-lab/command-dispatcher
To be completed
See detailed implementation proposal: jeckel-lab/query-dispatcher
Each layer has it's own Exception interface that extends Throwable
:
In each layer, when we need to throw an Exception, we create a new class corresponding to the type of Exception. This class must: