2

I'm refactoring a project using DDD, but am concerned about not making too many Entities their own Aggregate Root.

I have a Store, which has a list of ProductOptions and a list of Products. A ProductOption can be used by several Products. These entities seem to fit the Store aggregate pretty well.

Then I have an Order, which transiently uses a Product to build its OrderLines:

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;
    }
}

Looks like for now, DDD rules as respected. But I'd like to add a requirement, that might break the rules of the aggregate: the Store owner will sometimes need to check statistics about the Orders which included a particular Product.

That means that basically, we would need to keep a reference to the Product in the OrderLine, but this would never be used by any method inside the entity. We would only use this information for reporting purposes, when querying the database; thus it would not be possible to "break" anything inside the Store aggregate because of this internal reference:

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;
    }
}

Does this simple requirement dictates that Product becomes an aggregate root? That would also cascade to the ProductOption becoming an aggregate root, as Product has a reference to it, thus resulting in two aggregates which have no meaning outside a Store, and will not need any Repository; looks weird to me.

Any comment is welcome!

BenMorel
  • 34,448
  • 50
  • 182
  • 322

2 Answers2

3

Even though it is for 'reporting only' there is still a business / domain meaning there. I think that your design is good. Although I would not handle the new requirement by storing OrderLine -> Product reference. I would do something similar to what you already doing with product name and price. You just need to store some sort of product identifier (SKU?) in the order line. This identifier/SKU can later be used in a query. SKU can be a combination of Store and Product natural keys:

class Sku {
    private String _storeNumber;
    private String _someProductIdUniqueWithinStore;
}

class OrderLine {
    private Money _price;
    private int _quantity;
    private String _productName;
    private Sku _productSku;
}

This way you don't violate any aggregate rules and the product and stores can be safely deleted without affecting existing or archived orders. And you can still have your 'Orders with ProductX from StoreY'.

Update: Regarding your concern about foreign key. In my opinion foreign key is just a mechanism that enforces long-living Domain relationships at the database level. Since you don't have a domain relationship you don't need the enforcement mechanism as well.

Community
  • 1
  • 1
Dmitry
  • 17,078
  • 2
  • 44
  • 70
  • Thanks, that sounds a reasonable approach. We don't have a SKU, just an autogenerated `productId`. The only thing I regret with that approach is losing the benefit of using a foreign key in the DB (if I did, then deleting the Product would fail). Do you have any advise on this particular point? – BenMorel Sep 16 '11 at 08:40
0

In this case you need the information for reporting which has nothing to do with the aggregate root.

So the most suitable place for it would be a service (could be a domain service if it is related to business or better to application service like querying service which query the required data and return them as DTOs customizable for presentation or consumer.

I suggest you create a statistics services which query the required data using read only repositories (or preferable Finders) which returns DTOs instead of corrupting the domain with query models.

Check this

Community
  • 1
  • 1
Mohamed Abed
  • 5,025
  • 22
  • 31
  • Thanks, does that mean that it's OK to explicitly store the Product in the OrderLine: `$this->product = $product;`? – BenMorel Sep 15 '11 at 12:09