我正在使用DDD重构一个项目,但我担心的是没有让太多的实体成为他们自己的聚合根。
我有一个Store
,其中包含ProductOption
个列表和Product
个列表。多个ProductOption
可以使用Product
。这些实体似乎很适合Store
聚合。
然后我有Order
,它会暂时使用Product
来构建其OrderLine
:
class Order {
// ...
public function addOrderLine(Product $product, $quantity) {
$orderLine = new OrderLine($product, $quantity);
$this->orderLines->add($orderLine);
}
}
class OrderLine {
// ...
public function __construct(Product $product, $quantity) {
$this->productName = $product->getName();
$this->basePrice = $product->getPrice();
$this->quantity = $quantity;
}
}
现在看起来,DDD规则受到尊重。但是我想添加一个可能违反聚合规则的要求:商店所有者有时需要检查有关包含特定产品的订单的统计信息。
这意味着基本上,我们需要在OrderLine中保留对Product的引用,但实体内的任何方法都不会永远使用它。在查询数据库时,我们只会将此信息用于报告目的;因此,由于这个内部引用,不可能“破坏”Store聚合内的任何内容:
class OrderLine {
// ...
public function __construct(Product $product, $quantity) {
$this->productName = $product->getName();
$this->basePrice = $product->getPrice();
$this->quantity = $quantity;
// store this information, but don't use it in any method
$this->product = $product;
}
}
这个简单的要求是否要求Product成为聚合根?这也将级联到ProductOption成为聚合根,因为Product有对它的引用,因此导致两个聚合在Store之外没有任何意义,并且不需要任何Repository;对我来说很奇怪。
欢迎任何评论!
答案 0 :(得分:3)
即使是“仅报告”,仍然存在业务/域名含义。我认为你的设计很好。虽然我不会通过存储OrderLine -> Product
引用来处理新要求。我会做一些类似于你已经在做的产品名称和价格。您只需要在订单行中存储某种产品标识符(SKU?)。此标识符/ SKU稍后可用于查询。 SKU可以是商店和产品自然键的组合:
class Sku {
private String _storeNumber;
private String _someProductIdUniqueWithinStore;
}
class OrderLine {
private Money _price;
private int _quantity;
private String _productName;
private Sku _productSku;
}
这样,您就不会违反任何聚合规则,并且可以安全地删除产品和商店,而不会影响现有或已存档的订单。您仍然可以通过StoreY获得“ProductX订单”。
更新:关于您对外键的关注。在我看来,外键只是一个机制,它在数据库级别强制执行长期存在的域关系。由于您没有域关系,因此您也不需要强制机制。
答案 1 :(得分:0)
在这种情况下,您需要报告的信息与聚合根无关。
因此,最适合它的地方是服务(如果它与业务相关,则可以是域服务,或者更好地应用于查询所需数据的查询服务等应用服务,并将其作为可自定义的DTO返回给演示或消费者。
我建议您创建一个统计服务,使用只读存储库(或更好的Finders)查询所需数据,这些存储库返回DTO而不是使用查询模型破坏域。
检查this