DDD,状态对象/值对象

时间:2017-12-08 21:26:37

标签: php domain-driven-design value-objects

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

问题#1 - 实体或价值对象?

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

Proposal在任何时候都不会更改ProposalStatus的属性,它只会更改ProposalStatus所具有的属性。

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

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

  • 我可以从ProposalRepository中获取它......但是所有内容都是通过Proposal的聚合根来访问的,所以没有意义。
  • 我可以使用与ProposalStatus的$ id匹配的常量,但这似乎不对。
  • 我可以创建一个ProposalStatusRepository ......但是我应该从Proposal中访问另一个存储库吗?
  • 我可以使用$ id作为关键字创建所有可能状态的数组并添加到提案中,但这与存储库没有太大区别......

示例:

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
    }
}

3 个答案:

答案 0 :(得分:1)

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

/**
 * 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...
}

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

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

如果您的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来实现您的规范。