When working with domain models and POCO classes, where do queries go?
Asked Answered
C

5

7

I am new to domain models, POCO and DDD, so I am still trying to get my head around a few ideas.

One of the things I could not figure out yet is how to keep my domain models simple and storage-agnostic but still capable of performing some queries over its data in a rich way.

For instance, suppose that I have an entity Order that has a collection of OrdemItems. I want to get the cheapest order item, for whatever reason, or maybe a list of order items that are not currently in stock. What I don't want to do is to retrieve all order items from storage and filter later (too expensive) so I want to end up having a db query of the type "SELECT .. WHERE ITEM.INSTOCK=FALSE" somehow. I don't want to have that SQL query in my entity, or any variation of if that would tie me into a specific platform, like NHibernate queries on Linq2SQL. What is the common solution in that case?

Chant answered 6/2, 2009 at 18:48 Comment(0)
D
3

Domain objects should be independent of storage, you should use the Repostiory pattern, or DAO to persist the objects. That way you are enforcing separation of concerns, the object itself should not know about how it is stored.

Ideally, it would be a good idea to put query construction inside of the repository, though I would use an ORM inside there.

Here's Martin Fowler's definition of the Repository Pattern.

Downbeat answered 6/2, 2009 at 19:4 Comment(14)
So from inside my Order class I would do something like _orderRepository.GetOrderItemsNotInStock(this)?Chant
No, you would have a class OrderRepository, that would have a function like GetById(OrderId). The repository exists outside of the Order class, the order class would ideally be pretty small, probably just properties with getters and setters, as well as relevant logicDownbeat
relevant logic -> relevant business logic. Though I perfer to create service classes that actually carryout much of the business logic, but I'm a little fuzy about whether service classes are a part of DDD.Downbeat
GetById(OrderId) would get me the Order class populated, but what about the order items not in stock? They are part of the Order aggregate root, so in a way I think that Orders should know what constitutes an out-of-stock item, but at the same time my repository knows how to query them..I'm confusedChant
To add to my comment, if one has the repository knowing what is an out of stock item so it can query it, isn't that breaking the SoC? I think that the Order is the one that should know that, but at the same time it can't tell how to translate that into an actual optimized query, so I don't knowChant
Well the Order Repository could either automatically retrieve the order items for that order. Or alternatively you can create a second repository: OrderItemRepository, that has a GetByOrderId(OrderId)Downbeat
You would give the repository all the information it needs to get what you want, GetByOrderId(int OrderId, bool OutOfStockOnly) kind of thing.Downbeat
@m4bwav what Wookie seems to be trying to figure out in your solution (I am as well) as where the knowledge of the business goes. The idea in DDD is to have all the "intelligence" of the business in the domain classes, not in the repositories, right? Loading all items doesn't scale.Greenstone
When you do something like GetByOrderId(int OrderId, bool OutOfStockOnly) you are counting that the repository knows what an "out of stock" item means for my business (may be a flag in a table, or maybe a calculated field somewhere), does that follow DDD?Greenstone
Yeah, in this case he is saying it is a database column, so you would need to pass the OutOfStockOnly flag to the repository. The Repository's task is to handle all communication and whatever for the specific task of retrieving the item from wherever it is.Downbeat
The repository would either construct a query, or use an orm to figure out how to get the specific group of data.Downbeat
If an operation involves CRUD kind of stuff, then it goes in the repository. If you want to do some kind of filtering or ordering in code, then do it outside of the repository. IF you need to do it on a database level then put it in the repository.Downbeat
So if the customer/domain specialist says "An item is out of stock if it's not in the local warehouse and not in any warehouse in a 10-mile radius" you would break that into a few repo queries and then put it all together in your entity to return the list of out-of-stock items?Greenstone
I would put the repo queries in a separate service class, rather than in the domain object, but I'm not sure if that is necessary to be true DDD.Downbeat
E
3

Entities are the "units" of a domain. Repositories and services reference them, not vice versa. Think about it this way: do you carry the DMV in your pocket?

OrderItem is not an aggregate root; it should not be accessible through a repository. Its identity is local to an Order, meaning an Order will always be in scope when talking about OrderItems.

The difficulty of finding a home for the queries leads me to think of services. In this case, they would represent something about an Order that is hard for an Order itself to know.

Declare the intent in the domain project:

public interface ICheapestItemService
{
    OrderItem GetCheapestItem(Order order);
}

public interface IInventoryService
{
    IEnumerable<OrderItem> GetOutOfStockItems(Order order);
}

Declare the implementation in the data project:

public class CheapestItemService : ICheapestItemService
{
    private IQueryable<OrderItem> _orderItems;

    public CheapestItemService(IQueryable<OrderItem> orderItems)
    {
        _orderItems = orderItems;
    }

    public OrderItem GetCheapestItem(Order order)
    {
        var itemsByPrice =
            from item in _orderItems
            where item.Order == order
            orderby item.Price
            select item;

        return itemsByPrice.FirstOrDefault();
    }
}

public class InventoryService : IInventoryService
{
    private IQueryable<OrderItem> _orderItems;

    public InventoryService(IQueryable<OrderItem> orderItems)
    {
        _orderItems = orderItems;
    }

    public IEnumerable<OrderItem> GetOutOfStockItems(Order order)
    {
        return _orderItems.Where(item => item.Order == order && !item.InStock);
    }
}

This example works with any LINQ provider. Alternatively, the data project could use NHibernate's ISession and ICriteria to do the dirty work.

Expeditious answered 11/2, 2009 at 1:37 Comment(2)
I'm sure I'm missing something, but whenever I try to implement something like this, it doesn't work with "any" LINQ provider. For example the query in GetCheapestOrderItem may "look" different if I were using SDS, EF, or L2S, especially if I have to do joins or other non-trivial query logic.Ephah
That is a general worry about leaky abstractions. These queries are focused on the semantics of the model. It is up to the IQueryable implementation to translate those semantics to mechanics (the "looking different" part).Expeditious
M
2

As I understand this style of design, you would encapsulate the query in a method of an OrderItemRepository (or perhaps more suitably OrderRepository) object, whose responsibility is to talk to the DB on one side, and return OrderItem objects on the other side. The Repository hides details of the DB from consumers of OrderItem instances.

Murmuration answered 6/2, 2009 at 18:58 Comment(3)
Would you have this repository injected into the Order class (something line having an IOrderRepository as part of the Order constructor), or would you have it outside? If so, what would you have in the Order class? I am not sure where the knowledge/biz rule of what is an out of stock item goesChant
You'd want to pass the Repository to any domain object that needs an Order. I don't think it makes much sense for each Order instance to hold a reference to its Repository of origin, tho.Murmuration
@Murmuration I half agree: an Order shouldn't know anything about it's repository, but beyond that no domain object should know about any repository. If DomainObjectA needs an instance of DomainObjectB, some higher level co-ordinator should talk to their repositories and hook things up.Deibel
D
0

I would argue that it doesn't make sense to talk about "an Order that contains only the OrderItems that are not in stock". An "Order" (I presume) represents the complete list of whatever the client ordered; if you're filtering that list you're no longer dealing with an Order per se, you're dealing with a filtered list of OrderItems.

I think the question becomes whether you really want to treat Orders as an Aggregate Root, or whether you want to be able to pull arbitrary lists of OrderItems out of your data access layer as well.

You've said filtering items after they've come back from the database would be too expensive, but unless you're averaging hundreds or thousands of OrderItems for each order (or there's something else especially intensive about dealing with lots of OrderItems) you may be trying to optimize prematurely and making things more difficult than they need to be. I think if you can leave Order as the aggregate root and filter in your domain logic, your model will be cleaner to work with.

If that's genuinely not the case and you need to filter in the database, then you may want to consider having a separate OrderItem repository that would provide queries like "give me all of the OrderItems for this Order that are not in stock". You would then return those as an IList<OrderItem> (or IEnumerable<OrderItem>), since they're not a full Order, but rather some filtered collection of OrderItems.

Deibel answered 11/2, 2009 at 2:17 Comment(0)
G
-2

In the service layer.

Goliard answered 11/2, 2009 at 1:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.