Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD: entity's collection and repositories

Suppose I have

public class Product: Entity
{
   public IList<Item> Items { get; set; }
}

Suppose I want to find an item with max something... I can add the method Product.GetMaxItemSmth() and do it with Linq (from i in Items select i.smth).Max()) or with a manual loop or whatever. Now, the problem is that this will load the full collection into memory.

The correct solution will be to do a specific DB query, but domain entities do not have access to repositories, right? So either I do

productRepository.GetMaxItemSmth(product)

(which is ugly, no?), or even if entities have access to repositories, I use IProductRepository from entity

product.GetMaxItemSmth() { return Service.GetRepository<IProductRepository>().GetMaxItemSmth(); }

which is also ugly and is a duplication of code. I can even go fancy and do an extension

public static IList<Item> GetMaxItemSmth(this Product product)
{
   return Service.GetRepository<IProductRepository>().GetMaxItemSmth();
}

which is better only because it doesn't really clutter the entity with repository... but still does method duplication.

Now, this is the problem of whether to use product.GetMaxItemSmth() or productRepository.GetMaxItemSmth(product)... again. Did I miss something in DDD? What is the correct way here? Just use productRepository.GetMaxItemSmth(product)? Is this what everyone uses and are happy with?

I just don't feel it is right... if I can't access a product's Items from the product itself, why do I need this collection in Product at all??? And then, can Product do anything useful if it can't use specific queries and access its collections without performance hits?

Of course, I can use a less efficient way and never mind, and when it's slow I'll inject repository calls into entities as an optimization... but even this doesn't sound right, does it?

One thing to mention, maybe it's not quite DDD... but I need IList in Product in order to get my DB schema generated with Fluent NHibernate. Feel free to answer in pure DDD context, though.

UPDATE: a very interesting option is described here: http://devlicio.us/blogs/billy_mccafferty/archive/2007/12/03/custom-collections-with-nhibernate-part-i-the-basics.aspx, not only to deal with DB-related collection queries, but also can help with collection access control.

like image 829
queen3 Avatar asked Aug 31 '09 08:08

queen3


People also ask

What is a DDD repository?

In DDD, a repository is an objcect that participates in the domain but really abstracts away storage and infrastructure details. Most systems have a persistent storage like a database for its fully functioning. Applying repositories happens by integrating and synchronizing with existing aggregate objects in the system.

What is an entity in DDD?

In domain-driven design, an entity is a representation of an object in the domain. It is defined by its identity, rather than its attributes. It encapsulates the state of that object through its attributes, including the aggregation of other entities, and it defines any operations that might be performed on the entity.

Is repository part of domain model?

A repository object is the part of the domain model that interacts with storage such as the database, external sources, and so on, to retrieve the persisted objects.

What does the repository pattern do?

The Repository pattern makes it easier to test your application logic. The Repository pattern allows you to easily test your application with unit tests. Remember that unit tests only test your code, not infrastructure, so the repository abstractions make it easier to achieve that goal.


2 Answers

Having an Items collection and having GetXXX() methods are both correct.

To be pure, your Entities shouldn't have direct access to Repositories. However, they can have an indirect reference via a Query Specification. Check out page 229 of Eric Evans' book. Something like this:

public class Product
{
    public IList<Item> Items {get;}

    public int GetMaxItemSmth()
    {
        return new ProductItemQuerySpecifications().GetMaxSomething(this);
    }
}

public class ProductItemQuerySpecifications()
{
   public int GetMaxSomething(product)
   {
      var respository = MyContainer.Resolve<IProductRespository>();
      return respository.GetMaxSomething(product);
   }
}

How you get a reference to the Repository is your choice (DI, Service Locator, etc). Whilst this removes the direct reference between Entity and Respository, it doesn't reduce the LoC.

Generally, I'd only introduce it early if I knew that the number of GetXXX() methods will cause problems in the future. Otherwise, I'd leave it for a future refactoring exercise.

like image 135
Vijay Patel Avatar answered Sep 23 '22 05:09

Vijay Patel


I believe in terms of DDD, whenever you are having problems like this, you should first ask yourself if your entity was designed properly.

If you say that Product has a list of Items. You are saying that Items is a part of the Product aggregate. That means that, if you perform data changes on the Product, you are changing the items too. In this case, your Product and it's items are required to be transactionally consistent. That means that changes to one or another should always cascade over the entire Product aggregate, and the change should be ATOMIC. Meaning that, if you changed the Product's name and the name of one of it's Items and if the database commit of the Item's name works, but fails on the Product's name, the Item's name should be rolled back.

This is the fact that Aggregates should represent consistency boundaries, not compositional convenience.

If it does not make sense in your domain to require changes on Items and changes on the Product to be transactionally consistent, then Product should not hold a reference to the Items.

You are still allowed to model the relationship between Product and items, you just shouldn't have a direct reference. Instead, you want to have an indirect reference, that is, Product will have a list of Item Ids.

The choice between having a direct reference and an indirect reference should be based first on the question of transactional consistency. Once you have answered that, if it seemed that you needed the transactional consistency, you must then further ask if it could lead to scalability and performance issues.

If you have too many items for too many products, this could scale and perform badly. In that case, you should consider eventual consistency. This is when you still only have an indirect reference from Product to items, but with some other mechanism, you guarantee that at some future point in time (hopefully as soon as possible), the Product and the Items will be in a consistent state. The example would be that, as Items balances are changed, the Products total balance increases, while each item is being one by one altered, the Product might not exactly have the right Total Balance, but as soon as all items will have finished changing, the Product will update itself to reflect the new Total Balance and thus return to a consistent state.

That last choice is harder to make, you have to determine if it is acceptable to have eventual consistency in order to avoid the scalability and performance problems, or if the cost is too high and you'd rather have transactional consistency and live with the scalability and performance issues.

Now, once you have indirect references to Items, how do you perform GetMaxItemSmth()?

In this case, I believe the best way is to use the double dispatch pattern. You create an ItemProcessor class:

public class ItemProcessor
{
    private readonly IItemRepository _itemRepo;
    public ItemProcessor(IItemRepository itemRepo)
    {
        _itemRepo = itemRepo;
    }

    public Item GetMaxItemSmth(Product product)
    {
        // Here you are free to implement the logic as performant as possible, or as slowly
        // as you want.

        // Slow version
        //Item maxItem = _itemRepo.GetById(product.Items[0]);
        //for(int i = 1; i < product.Items.Length; i++)
        //{
        //    Item item = _itemRepo.GetById(product.Items[i]);
        //    if(item > maxItem) maxItem = item;
        //}

        //Fast version
        Item maxItem = _itemRepo.GetMaxItemSmth();

        return maxItem;
    }
}

And it's corresponding interface:

public interface IItemProcessor
{
    Item GetMaxItemSmth(Product product);
}

Which will be responsible for performing the logic you need that involves working with both your Product data and other related entities data. Or this could host any kind of complicated logic that spans multiple entities and don't quite fit in on any one entity per say, because of how it requires data that span multiple entities.

Than, on your Product entity you add:

public class Product
{
    private List<string> _items; // indirect reference to the Items Product is associated with
    public List<string> Items
    {
        get
        {
            return _items;
        }
    }

    public Product(List<string> items)
    {
        _items = items;
    }

    public Item GetMaxItemSmth(IItemProcessor itemProcessor)
    {
        return itemProcessor.GetMaxItemSmth(this);
    }
}

NOTE: If you only need to query the Max items and get a value back, not an Entity, you should bypass this method altogether. Create an IFinder that has a GetMaxItemSmth that returns your specialised read model. It's ok to have a separate model only for querying, and a set of Finder classes that perform specialized queries to retrieve such specialized read model. As you must remember, Aggregates only exist for the purpose of data change. Repositories only work on Aggregates. Therefore, if no data change, no need for either Aggregates or Repositories.

like image 28
Didier A. Avatar answered Sep 22 '22 05:09

Didier A.