views:

778

answers:

5

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%5Fmccafferty/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.

+1  A: 

I think that this is a difficult question that has no hard and fast answer.

A key to one answer is to analyze Aggregates and Associations as discussed in Domain-Driven Design. The point is that either you load the children together with the parent or you load them separately.

When you load them together with the parent (Product in your example), the parent controls all access to the children, including retrieval and write operations. A corrolary to this is that there must be no repository for the children - data access is managed by the parent's repository.

So to answer one of your questions: "why do I need this collection in Product at all?" Maybe you don't, but if you do, that would mean that Items would always be loaded when you load a Product. You could implement a Max method that would simply find the Max by looking over all Items in the list. That may not be the most performant implementation, but that would be the way to do it if Product was an Aggregate Root.

What if Product is not an Aggregate Root? Well, the first thing to do is to remove the Items property from Product. You will then need some sort of Service that can retrieve the Items associated with the Product. Such a Service could also have a GetMaxItemSmth method.

Something like this:

public class ProductService
{
    private readonly IItemRepository itemRepository;

    public ProductService (IItemRepository itemRepository)
    {
        this.itemRepository = itemRepository;
    }

    public IEnumerable<Item> GetMaxItemSmth(Product product)
    {
        var max = this.itemRepository.GetMaxItemSmth(product);
        // Do something interesting here
        return max;
    }
}

That is pretty close to your extension method, but with the notable difference that the repository should be an instance injected into the Service. Static stuff is never good for modeling purposes.

As it stands here, the ProductService is a pretty thin wrapper around the Repository itself, so it may be redundant. Often, however, it turns out to be a good place to add other interesting behavior, as I have tried to hint at with my code comment.

Mark Seemann
Basically you tell that if I need some operation on subitems (which is 90% case) then I can't have the collection inside entity... and instead of doing lazy load and all funny stuff with NHibernate (i.e. have collection in entity) I need to add manual methods to repositories for GetItemsByProduct(product), etc? Doesn't sound right. I'll better do both product.Items and productRepository.GetMaxItemSmth(product)... at least less manual work and no problems except different Intellisense entry point ;-)
queen3
Yes, lazy or implicit loading is at odds with DDD which states that everything should be explicitly modeled. I'm not saying that it is the best way, or the only way to do things - I'm just saying that this is the DDD way. Implicitly loading navigation properties is not. It's not wrong to do that, it's just a different philosophy - but you asked about it in a DDD context :)
Mark Seemann
+1  A: 
Dom Ribaut
I see the point of your and Mark's answers. With old-fashioned manual repository (not NHibernate) I wouldn't probably even think about Items - unless I needed them. But with Items helping me to generate DB schema... Anyway, now I do get this feeling that NHibernate makes it too easy to violate good design ;-) I think the answer is "yes, use repository" but I'm still not sure why having Order Items separately from Order makes good domain classes... after all it's not natural. Items are very nice for adding/deleting/etc, except filtering.
queen3
And I can just pass Product to page and have items displayed... while with separate repository I'll need to grab them from another repository, pack into separate DTO object for the view... Now I see why Services are so important for pure DDD - these extra work has to be performed somewhere ;-)))
queen3
A: 

Remember that NHibernate is a mapper between the database and your objects. Your issue appears to me that your object model is not a viable relational model, and that's ok, but you need to embrace that.

Why not map another collection to your Product entity that uses the power of your relational model to load in an efficient manner. Am I right in assuming that the logic to select this special collection is not rocket science and could easily be implemented in filtered NHibernate mapped collection?

I know my answer has been vague, but I only understand your question in general terms. My point is that you will have problems if you treat your relational database in an object oriented manner. Tools like NHibernate exist to bridge the gap between them, not to treat them in the same way. Feel free to ask me to clarify any points I didn't make clear.

Stefan Moser
That's true, it is not clear... it seems like you concentrate on how to load the collection which I have no problem with. But, however I load the collection, in order to calculate MAX() I'll need to send ALL its data over the wire to my C# app. I need a separate method that will perform the query on DB and send me the result in ANY case. The question is can my entities use this method if needed (via DI)... and the best answer I see for now is "it depends".
queen3
Instead of having a separate method to query the DB and worrying about whether or not to inject a repository, have a separate *property* and use a custom NHibernate mapping to load the value when the entity is loaded. Use the power of the relational model to do what it is good at instead of trying to shoehorn the same logic into your OO model.
Stefan Moser
I don't understand, how do I load result of an aggregate SQL query (SELECT MAX()) into a property and how do I make this query to execute only when property is accessed instead of when entity is loaded? And one thing I totally missed... how do I calculate max for a "new Product()" - which hasn't been stored in DB yet? Do I provide 2 implementations of GetMax() - one for memory object and another for already persisted? Smth like "if (Id == 0) return this.Max() else return injectedRepository.Max()"...
queen3
Yes, you can load aggregate data into a property using NHibernate. If you need to track changes to items and new products, you can do that, Product is your aggregate root after all.
Stefan Moser
A: 

Another way you can solve this problem is to track it all in the aggregate root. If Product and Item are both part of the same aggregate, with Product being the root, then all access to the Items is controlled via Product. So in your AddItem method, compare the new Item to the current max item and replace it if need be. Maintain it where it's needed within Product so you don't have to run the SQL query at all. This is one reason why defining aggregates promotes encapsulation.

Stefan Moser
A nice method, however, when Product is loaded by NH which do not call my AddItem()... I do not get max. And it's not always good to calculate Max() all the time just in case - maybe nobody needs it. If I need 10 such calculations should I perform all of them each time Item is added/removed...
queen3
Why do you need to recalculate Max when it's loaded from NHibernate? Do you have other systems that use the same DB and will change data without going through your app? I'm also assuming that a simple comparison of a new item to the current Max item is trivial in cost, if you feel otherwise, then discount this approach.
Stefan Moser
Hm, now I see the point better. This is also a nice solution, though I think it's better to apply it when performance is bad (as with any optimization), especially comparing to the cost of additional field in DB just for the purpose (imagine several calculations). Still an acceptable solution in many cases.
queen3
+2  A: 

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.

Vijay Patel
I accept it because it complies with how I feel it's easier to deal with the problem, this doesn't mean it's the right answer (are there any, after all? ;-).
queen3