jeckel-lab / contract
Contract / 其他包和DDD项目使用的接口
v2.5.1
2024-03-25 15:09 UTC
Requires
- php: ^8.0
- ext-json: *
- psr/http-message: ^1.0
Requires (Dev)
- infection/infection: ^0.25
- maglnet/composer-require-checker: ^3||^4
- phpmd/phpmd: ^2.11
- phpro/grumphp: ^1.5
- phpstan/phpstan: ^1.2
- phpunit/phpunit: ^9.5
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.6
- vimeo/psalm: ^4.12
Suggests
- vimeo/psalm: Using psalm with this 'contract' will enable immutability and strong typing verification
README
Jeckel-Lab Contract
其他包或DD项目中用作合同的接口列表
此合同包含一些强类型定义、对象关系和psalm验证。
需要 php >= 7.2.*
和 php >= 8.0
2.x版本(php >= 8.0)的文档
域
域合同是DDD实现建议的一部分,不是必需的,也不与任何框架相关联。
身份
Identity用于定义一个实体或根聚合的唯一标识符。
身份必须是
- 不可变的
- 最终的
- 构造函数应该是私有的,使用工厂方法
new
==> 生成(如果可能)一个具有随机值的新Identity对象(如UUIDs)from
==> 从现有值实例化Identity
请参阅详细实现方案:jeckel-lab/identity-contract
实体
Entity:主要 实体 合同
实体 必须 有一个实现Identity
接口的Id。
别忘了使用@psalm模板
/** * 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是关于在用例期间发生的事情的通知。
事件 必须 是
- 不可变的
DomainEventAware
实体和根聚合处理领域事件。为了便于这种行为,您可以使用此接口和特质
此接口定义了两个方法
/** * @param Event ...$events * @return static */ public function addDomainEvent(Event ...$events): static; /** * @return list<Event> */ public function popEvents(): array;
addDomainEvent
允许您注册在用例期间发生的新事件。popEvent
将在用例结束时清空实体的事件列表,以便将它们发送到事件分发器。
只需将接口和特质用于您的实体即可
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; } //... }
如果您使用命令总线模式,则可以轻松地将事件添加到响应中
new CommandResponse(events: $entity->popEvents());
值对象
使用ValueObject
将值(或复杂类型的一组值)作为对象嵌入,允许您
- 在应用程序中使用强类型(一个
Speed
不能与任何随意的浮点数混合) - 嵌入数据验证(确保
Speed
始终是正数,小于一个合理的值等。)
值对象必须定义为
- 不可变的(一旦实例化,它们就不应被修改,除非创建了一个新实例)。
- 最终的
- 构造函数应该是私有的,使用静态的
from
方法作为工厂 - 当请求具有相同值的ValueObject时,
from
应该返回相同的实例
请考虑这样实现它
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
核心
待完成
命令分发器
待完成
请参阅详细实现方案:jeckel-lab/command-dispatcher
查询分发器
待完成
请参阅详细实现方案:jeckel-lab/query-dispatcher
异常
每个层都有自己的异常接口,该接口扩展Throwable
- 核心: CoreException
- 域: DomainException
- 基础设施: InfrastructureException
- 表示层: PresentationException
在每个层中,当我们需要抛出异常时,我们会创建一个对应异常类型的新类。这个类必须
- 继承自SPL异常或同一命名空间中的另一个(更通用的)异常。
- 实现当前层的异常接口。