【问题标题】:DDD, PHP. Domain Object and Business LogicDDD,PHP。领域对象和业务逻辑
【发布时间】:2016-12-06 08:04:54
【问题描述】:

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

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

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

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

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

提前致谢。

【问题讨论】:

    标签: php model domain-driven-design domain-object


    【解决方案1】:

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

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

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

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

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

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

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

    是的,正确的

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

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

    【讨论】:

    • 感谢您的有用回答。所以 - 让我们确认我的理解是否正确。在我们的例子中,Invoice 是一个聚合。 Aggregate Root 是 Invoice 本身,但它也是 Entity,对吗?它有自己的身份(发票号码是唯一的)。那么什么是 InvoiceItems?我可以直接从 InvoiceItem 存储库中获取它们(如果我应该创建这样的存储库),还是我总是只需要在 Aggregate 上操作?
    【解决方案2】:

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

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

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

    【讨论】:

    • 感谢您的回答。如果我们将很多业务逻辑附加到单个域对象上,它不会与 SRP 冲突吗?
    • DDD 希望启用良好的 OO 设计。继续遵循单一职责原则和其他明智的 OO 实践。
    • @hopsey 请记住,您的域不仅仅是域对象。业务逻辑不仅限于存在于聚合根和实体中。它们可以存在于多个地方,包括域服务、命令等。域是代表通用语言的类的凝聚力集合。没有规则,因为没有更好的词,说业务规则必须存在于聚合/实体中,而不是其他地方。其中很大一部分将归结为您的特定实施和设计决策。
    【解决方案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);
    }
    

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

    【讨论】:

      猜你喜欢
      • 2011-08-01
      • 1970-01-01
      • 2012-05-27
      • 2014-02-24
      • 1970-01-01
      • 1970-01-01
      • 2017-11-06
      • 2023-03-26
      相关资源
      最近更新 更多