8

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

其中之一是对以下问题的回答:Domain Objects 中应该存在多少业务逻辑?一些消息来源说域对象应该与整个业务逻辑相关联,另一方面,我遇到了一些文章,我认为它应该尽可能小,并且只代表它的值。这让我真的很困惑。

在我的理解中,域对象是类,代表域中的实体。

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

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()。我们在这里对其他域对象进行操作。

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

提前致谢。

4

3 回答 3

4

域对象中应该存在多少业务逻辑?[...] 我遇到了一些文章,我认为它应该尽可能小并且只代表它的价值。

请注意几乎完全由数据组成且缺乏行为的贫血域模型。DDD 是关于创建一个行为丰富的域模型。因此,可以将逻辑添加到域类中。

DDD 强调良好的面向对象设计,将方法和数据放在一起,从而促进高度内聚的系统

于 2016-08-01T21:14:27.600 回答
3

我不确定这类问题是否有正确答案,因为应用 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);
}

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

于 2016-08-01T10:10:17.697 回答
3

域对象中应该存在多少业务逻辑?

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

域对象是类,代表域中的实体。

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

但我不确定 getInvoiceValue()。我们在这里对其他域对象进行操作。

您给出的 Invoice 示例是 Aggregate 的经典示例 - Invoice 将包含 InvoiceItems。getInvoiceValue() 在这里很好。

在我们的例子中,Invoice 是一个聚合。Aggregate Root 是 Invoice 本身,但它也是 Entity,对吗?它有自己的身份(发票号码是唯一的)。

是,对的

那么什么是 InvoiceItems?我可以直接从 InvoiceItem 存储库中获取它们(如果我应该创建这样的存储库),还是我总是只需要在 Aggregate 上操作?

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

于 2016-08-01T11:55:41.030 回答