我最近一直忙于尝试理解ddd和Model层的概念。阅读大量的文章,例子,Q和A,花了很多时间。而且我还不确定我是否有正确的原则。
其中一个就是问题的答案:Domain Objects中应该存在多少业务逻辑?有些消息来源说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()。我们在这里运行其他域对象。
也许我们应该只使用域对象来表示数据,但是使用一些装饰器来执行更高级的任务?
提前致谢。
答案 0 :(得分:4)
Domain Objects中应该存在多少业务逻辑? [...]我遇到了一些文章,我认为它应该尽可能小,只代表它的价值。
谨防几乎完全由数据组成且缺乏行为的Anemic Domain Model。 DDD是关于创建一个行为丰富的域模型。因此,将逻辑添加到域类中是很好的。
DDD强调良好的面向对象设计,将方法和数据放在一起,从而高度推广cohesive systems。
答案 1 :(得分: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);
}
同样,它实际上取决于您的特定域的工作方式,但正如您所看到的,实现不应该是一个大问题。更多关于它如何适合您的领域。
答案 2 :(得分:2)
Domain Objects中应该存在多少业务逻辑?
所有这些(如果可能的话)。 DDD的目的是捕获您域中的业务逻辑 - 这里可以使用各种战术模式来帮助(聚合,实体,值对象,域服务等......)。
域对象是表示域中实体的类。
您域中的类可以代表的不仅仅是实体。聚合,实体,价值对象,域服务等都可以由您域中的类表示。
但我不确定getInvoiceValue()。我们在这里运行其他域对象。
您给Invoice的示例是Aggregate的典型示例 - Invoice将包含InvoiceItems。 getInvoiceValue()在这里很好。
在我们的案例中,Invoice是一个聚合。 Aggregate Root是Invoice本身,但它也是Entity,对吧?它有自己的身份(发票号是唯一的)。
是的正确
那么InvoiceItems是什么?我可以直接从InvoiceItem存储库中获取它们(如果我应该创建它),还是我总是只需要在Aggregate上运行?
这取决于您的使用案例。它有助于将写入和读取模型分开(CQRS)。如果您正在谈论读取方(即报告),那么您将绕过域模型并拥有代表您的读取模型的对象。这可能只是一个数据库查询。如果您正在谈论您的写入方(即命令,域),那么通常每个聚合根都有一个存储库。你的聚合根源是一个建模问题。您希望以强制执行所有业务规则的方式创建它们 - 在您的示例中,如果发票需要加载InvoiceItems以强制执行规则(例如,“每个发票不超过5个项目”),则是,它们应该是通过聚合根
加载