DDD,PHP。领域对象和业务逻辑

DDD, PHP. Domain Object and Business Logic

最近一直忙于理解ddd和模型层的概念。阅读大量文章、示例、问答,花了很多时间在上面。而且我仍然不确定我的一些原则是否正确。

其中之一是问题的答案:域对象中应该存在多少业务逻辑?一些消息来源说域对象应该附加到整个业务逻辑,另一方面,我看到一些文章,我认为它应该尽可能小,并且只代表它的值。这让我很困惑。

在我看来,域对象是 类,表示域中的实体。

因此,让我们以发票实体为例。每张发票都包含其项目。要计算发票价值,我们必须将所有项目价值相加(这是一个非常简单的例子,在现实世界中会有加税、计算支付价值等情况)

class Invoice
{
    public $id;
    public $items = [];
    public $status;

    const STATUS_PAID = 'paid';
    const STATUS_NOT_PAID = 'not_paid';

    public function isPaid()
    {
        return $this->status == self::STATUS_PAID;
    }

    public function getInvoiceValue()
    {
        $sum = 0;
        foreach($this->items as $item) {
            $sum += $item->value;
        }
        return $sum;
    }
}

根据我的理解,方法 isPaid() 在正确的位置。它指的是它自己的数据。但我不确定 getInvoiceValue()。我们在这里对其他域对象进行操作。

也许我们应该只使用领域对象来表示数据,而使用一些装饰器来执行更高级的任务?

提前致谢。

我不确定这类问题是否有正确答案,因为应用 DDD 实际上取决于您应用它的特定领域。如果满足业务需求,您的实施在某些地方可能是完全有效的。在其他情况下,就像您提到的税收等,它不会。所以我想说的是,在将它们转化为代码之前,您需要不断询问有关您的领域的问题,以充分了解您的需求。

话虽如此,如果您有一个更复杂的场景,需要对外部世界有一些额外的了解才能得出发票的价值,一种选择是在您的域中明确表示。在您的示例中,它可能是一个 InvoiceProducer,它可能具有如下签名:

class InvoiceProducer {

    public function __construct(TaxProvider $taxProvider) {
        $this->taxProvider = $taxProvider;
    }

    public function invoiceFor(array $items) {
        new Invoice($items, $this->calculateValue($items));
    }

    private function calculateValue(array $items) {
        $sum = array_reduce($items, function($acc, $item){
            $acc += $item->value;
        }

        return $this->taxProvider->applyTaxTo($sum);
    }
}

另一种选择是使用某种策略模式,这将使您的实施方式与现在的方式非常相似,但您将通过您希望计算税收的方式进行调用:

public function getInvoiceValue(TaxProvider $taxProvider)
{
    $sum = 0;
    foreach($this->items as $item) {
        $sum += $item->value;
    }

    return $taxProvider->applyTaxFor($sum);
}

同样,这实际上取决于您的特定域的工作方式,但如您所见,实施应该没什么大不了的。更多关于它如何适合您的领域。

How much business logic should exist in Domain Objects?

全部(如果可能)。 DDD 的重点是在您的域中捕获您的业务逻辑 - 可以在此处使用各种战术模式来提供帮助(聚合、实体、值对象、域服务等...)。

Domain objects are classes, that represent entities in the domain.

您域中的 类 可以代表的不仅仅是实体。聚合、实体、值对象、域服务等都可以在您的域中用 类 表示。

But I'm not sure about getInvoiceValue(). We operate here on other domain objects.

您给出的 Invoice 示例是聚合的典型示例 - Invoice 将包含 InvoiceItems。 getInvoiceValue() 在这里没问题。

In our case, Invoice is an aggregate. Aggregate Root is Invoice itself, but it is also Entity, right? It has its own identity though (invoice number that is unique).

是正确的

What are InvoiceItems then? Can I fetch them directly from InvoiceItem repository (if I should create such), or do I always have to operate only on Aggregate?

这取决于您的用例。它有助于将写入和读取模型分开 (CQRS)。如果您正在谈论读取端(即报告),那么您将绕过域模型并拥有代表您的读取模型的对象。这可能只是一个数据库查询。如果您谈论的是写入端(即命令、域),那么通常每个聚合根都有一个存储库。您的聚合根是什么是建模问题。您希望以强制执行所有业务规则的方式创建它们 - 在您的示例中,如果发票需要加载 InvoiceItems 以强制执行规则(例如 "no more than 5 items per invoice")那么是的,它们应该通过聚合加载根

How much business logic should exist in Domain Objects? [...] I came across articles where I assumed it should be as tiny as possible and only represent its values.

当心 Anemic Domain Model,它几乎完全由数据组成且缺乏行为。 DDD 是关于创建一个行为丰富的域模型。因此可以将逻辑添加到域 类.

DDD 强调良好的面向对象设计,将方法和数据放在一起,从而高度提升 cohesive systems