jeckel-lab/contract

Contract / 其他包和DDD项目使用的接口

v2.5.1 2024-03-25 15:09 UTC

This package is auto-updated.

Last update: 2024-09-25 16:10:07 UTC


README

Latest Stable Version Total Downloads Build Status codecov Mutation testing badge

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

在每个层中,当我们需要抛出异常时,我们会创建一个对应异常类型的新类。这个类必须

  • 继承自SPL异常或同一命名空间中的另一个(更通用的)异常。
  • 实现当前层的异常接口。