DDD,状态对象/值对象

DDD, Status object / Value Objects

我有一个 class 提案,它的 $status 类型为 ProposalStatus。现在,在大多数情况下,ProposalStatus 的身份不会改变......但它确实(有点)。它的 $id 是固定的,但是 $display_name 和 $definition (只是字符串)可以随着主数据的更新在很长一段时间内发生变化,但它不会在生命周期内发生变化HTTP 请求-响应.

问题 #1 - 实体还是值对象?

值对象是从不 应该在特定应用程序执行的生命周期内改变还是永远不应该改变的东西?如果更改了显示名称或定义,那么我真的希望/希望它为每个人都更改。但是,由于它们 可以在提案之外定义 我认为直接使它们成为实体而不是值对象。

提案绝不会更改 ProposalStatus 的属性,它只会更改 ProposalStatus 的属性。

问题 #2 - 如何为域驱动设计正确设置状态?

我的提案对象能够管理它的状态,但为了做到这一点,我需要有一个特定的 ProposalStatus 对象。只是,允许它 returns 预期的权利的状态列表在哪里?

示例:

class ProposalStatus {
    protected $id; // E.g., pending_customer_approval
    protected $display_name; // E.g., Pending Customer Approval
    protected $definition; // E.g., The proposal needs to be approved by the customer
}

class Proposal {
    /**
     * The current status of the proposal
     * @var ProposalStatus
     */
    protected $proposal_status;

    public function withdraw() {
        // verify status is not closed or canceled
        // change status to draft
    }

    public function submit() {
        // verify status is draft
        // change status to pending customer approval
    }

    public function approve() {
        // verify status is pending customer approval
        // change status to approved
    }

    public function reject() {
        // verify status is pending customer approval
        // change status to rejected
    }

    public function close() {
        // verify status is not canceled
        // change status to closed
    }

    public function cancel() {
        // verify status is not closed
        // change status to canceled
    }
}

所有可能的提案状态列表是静态的吗?我觉得是这样的。所以 ProposalStatus 看起来像一个简单的枚举。 DisplayName、Definition等属性与业务代码无关。

只需将 ProposalStatus 定义为枚举(带有只读字段或您的语言支持的任何其他结构的静态 class)。它应该在业务层定义。业务代码应该能够区分枚举值(例如 if (proposal.Status == ProposalStatus.Pending) { poposal.Status = ProposalStatus.Approved; }).

在应用程序甚至表示层定义一个字典,其中包含映射到 ProposalStatus 的 DisplayName 和 Definition。它只会在向用户显示数据时使用。

如果您的 ProposalStatus 是一个固定的值列表,只需使用枚举方法即可。

否则您需要将 ProposalStatus 视为用户可以创建、更新和删除的 AggregateRoot(我猜)。将 ProposalStatus 分配给 Proposal 时,您只需要 ID。如果你想检查给定的 ID 是否存在,你只需要用专门的查询来满足不变量。规范模式很适合这里。

class ProposalStatusExistsSpecification
{
    public function isSatisfiedBy(string $proposalSatusId): bool
    {
        //database query to see if the given ID exists
    }
}

您可以找到 here Interfaces 来实现您的规范。

据我从您的域中了解到,ProposalStatus 应该是 Value object。因此,它应该是不可变的并包含特定的行为。在您的情况下,该行为正在测试特定值并仅初始化为允许的值范围。您可以使用带有私有构造函数和静态工厂方法的 PHP class。

/**
 * ProposalStatus is a Value Object
 */
class ProposalStatus
{
    private const DRAFT                     = 1;
    private const PENDING_CUSTOMER_APPROVAL = 2;
    private const CANCELLED                 = 3;
    private const CLOSED                    = 4;

    /** @var int */
    private $primitiveStatus;

    private function __construct(int $primitiveStatus)
    {
        $this->primitiveStatus = $primitiveStatus;
    }

    private function equals(self $another): bool
    {
        return $this->primitiveStatus === $another->primitiveStatus;
    }

    public static function draft(): self
    {
        return new static(self::DRAFT);
    }

    public function isDraft(): bool
    {
        return $this->equals(static::draft());
    }

    public static function pendingCustomerApproval(): self
    {
        return new static(self::PENDING_CUSTOMER_APPROVAL);
    }

    public function isPendingCustomerApproval(): bool
    {
        return $this->equals(static::pendingCustomerApproval());
    }

    public static function cancelled(): self
    {
        return new static(static::CANCELLED);
    }

    public function isCancelled(): bool
    {
        return $this->equals(static::cancelled());
    }

    public static function closed(): self
    {
        return new static(static::CLOSED);
    }

    public function isClosed(): bool
    {
        return $this->equals(static::closed());
    }
}

class Proposal
{
    /** @var ProposalStatus */
    private $status;

    public function __construct()
    {
        $this->status = ProposalStatus::draft();
    }

    public function withdraw()
    {
        if (!$this->status->isClosed() && !$this->status->isCancelled()) {
            $this->status = ProposalStatus::draft();
        }
    }

    // and so on...
}

请注意,不变性是值对象的一个​​重要特征。