I'm working on a legacy warehouse system. There is one Aggregate root, Product which has its correspondent ProductRepository.

Right now I have a new requirement that says the following:

Some Products are Purchasable and need to keep track of the datetime they have become purchasable.

So, in order to implement this requirement, I decided to take the following approach that, since I can see a "is-a" relationship, I decided to create a new class called PurchasableProduct which inherits Product and adds this new attribute.

class PurchasableProduct(Product):
    def __init__(product_properties, purchasable_datetime):
        self.purchasable_datetime = purchasable_datetime

What's bugging me right now are the repositories. ProductRepository, of course, should still return instances of Products (even though they might be PurchasableProducts), but I need a way to retrieve and save those PurchasableProducts. Adding a PurchasableProductsRepository seems a solution but it's kind of weird that I can have two repositories, ProductRepository and PurchasableRepository, that I can use to save instances of PurchasableProducts.

In a DDD paradigm, what would be the best way to implement this situation where an aggregate root is an specialization of another one?

  • 2
    Based on how the requirement is formulated, it seems to me that products can be changed between being purchasable and non-purchasable. This strongly suggests that being purchasable is not a special kind of product (for which a sub-class is appropriate), but rather a property of a product. – Bart van Ingen Schenau Oct 17 '17 at 12:38
  • @BartvanIngenSchenau That was exactly one of my thoughts, implement a state pattern on Product. However it feels that, if I follow that approach, I would require to directly modify the Product class, violating the Open/Closed principle and potentially affecting all clients of the Product class. What are your opinions on that regard? Thanks a lot for sharing your thoughts. – David Jiménez Martínez Oct 17 '17 at 12:44
  • In my view, the Open/Closed principle does not apply to the Product class when you get a new requirement that specifies a change the products. Would you also create a FooProduct subclass if the next requirement is "all products must be extended with a Foo attribute"? – Bart van Ingen Schenau Oct 17 '17 at 17:46

Your gut feeling is right, both Product and PurchasableProduct should be retrieved from the ProductRepository.

This shouldn't be an issue, as one inherits from the other, say we add an extra table to the database with the additional PurchasableProduct fields, left join it into the Product select statement and then where its not null instead of instantiating and populating a new Product, we instantiate a new PurchasableProduct. You can still return an array/List of Product. when calling the overridden Purchase() method for example the PurchasableProduct will check its date and the Product wont.

Essentially your class sub typing is just replacing a conditional statement in the Purchase method and some nullable fields on Product. Its neater code, but t shouldn't change the overall flow of the application.

If you have a specific need to get only the purchasable products, then you might add a GetProductsWhichArePurchasable() method(s) to the repository to allow for optimisation. Whether you still return a list of Products, or PurchasableProducts is interesting. I think a purist would stick with Product, but since your are optimising anyway......

  • Your answer makes sense on reusing the Product Repository for both specialisations. Though that means that the repository should be aware and coupled to this specialization to save/retrieve products and generating instances of one type or the other, right? Wouldn't it be a kind of Open-Closed principle violation regarding to the modifications the repository will require? Thanks! – David Jiménez Martínez Oct 17 '17 at 12:51
  • 1
    Not really, you could make a new ProductAndPurchasableProductRepo, which encapsulated the ProductRepo and added on an extra db select to add the PurchasableProducts, extending the original class if you wanted and the same if you invent some new type of product later. It would be good OOP but bad in practice, because you want to optimise for your data storage layer and also because you would prob never use the old ProductRepo. – Ewan Oct 17 '17 at 12:58
  • I guess, if you had separate dbs for each type of product you might do that, make two repos and then wrap them together. Im assuming that you are using a standard relational db and it will be altered to accomodate the new product type – Ewan Oct 17 '17 at 13:01
  • in either case though the app would use a product repo which returned all types of product. It doesnt care about the implementation – Ewan Oct 17 '17 at 13:03

it's kind of weird that I can have two repositories

Get used to it.

It's long been considered good practice to model your use cases explicitly. From the perspective of the application, it has a reference to a repository that plays the role of providing a reference to a particular flavor of aggregate root.

So you might have

interface Product {
    // ...

interface ProductRepository {
    Product get(Id id);

interface PurchasableProduct {
    // ...

interface PurchaseableProductRepository {
    PurchasableProduct get(Id id);

With use cases that need to access PurchaseableProducts wired to the PurchaseableProductsRepository.

The application doesn't need to know whether or not the underlying implementations are the same.

The underlying implementation can definitely get tricky; for example, you don't want to lose the purchasable date time if a product gets updated using the repository that doesn't know about that information.

The way I've come to think of it: the persisted representation of the aggregate is a message, from a past version of the domain model to the present (and future). So the basic principles of message compatibility still apply - we need must-ignore and must-forward semantics.

In short, if we use a ProductRepository to store a PurchaseableProduct, we need to be sure that the implementation doesn't overwrite the properties that it knows nothing about.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.