使用领域模型和 POCO 类时,查询会去哪里?

发布于 2024-07-13 16:42:56 字数 380 浏览 7 评论 0原文

我对领域模型、POCO 和 DDD 很陌生,所以我仍在尝试理解一些想法。

我还不明白的一件事是如何保持我的域模型简单且与存储无关,但仍然能够以丰富的方式对其数据执行一些查询。

例如,假设我有一个实体 Order,其中包含 OrdemItems 的集合。 无论出于何种原因,我想要获得最便宜的订单商品,或者可能是当前没有库存的订单商品列表。 我不想做的是从存储中检索所有订单项并稍后进行过滤(太昂贵),所以我想以某种方式最终得到类型为“SELECT .. WHERE ITEM.INSTOCK=FALSE”的数据库查询。 我不想在我的实体中使用该 SQL 查询,或者将我绑定到特定平台的任何变体,例如 Linq2SQL 上的 NHibernate 查询。 在这种情况下,常见的解决方案是什么?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

柠栀 2024-07-20 16:42:56

实体是域的“单元”。 存储库和服务引用它们,反之亦然。 试想一下:您的口袋里有 DMV 吗?

OrderItem 不是聚合根; 它不应该通过存储库访问。 它的标识是 Order 的本地标识,这意味着在讨论 OrderItem 时,Order 将始终位于范围内。

很难找到一个解决问题的地方,这让我想到了服务。 在这种情况下,它们将代表关于Order的某些内容,而Order本身很难知道。

在域项目中声明意图:

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

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

在数据项目中声明实现:

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

此示例适用于任何 LINQ 提供程序。 或者,数据项目可以使用 NHibernate 的 ISessionICriteria 来完成这些肮脏的工作。

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.

北城挽邺 2024-07-20 16:42:56

域对象应该独立于存储,您应该使用Repostiory模式或DAO来持久化对象。 通过这种方式,您可以强制关注点分离,对象本身不应该知道它是如何存储的。

理想情况下,将查询构造放在存储库中是个好主意,尽管我会在其中使用 ORM。

以下是 Martin Fowler 对存储库模式的定义。

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.

卷耳 2024-07-20 16:42:56

据我了解这种设计风格,您可以将查询封装在 OrderItemRepository (或更合适的 OrderRepository)对象的方法中,该对象的职责是一侧与数据库通信,并在另一侧返回 OrderItem 对象。 存储库向 OrderItem 实例的使用者隐藏数据库的详细信息。

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.

暗喜 2024-07-20 16:42:56

我认为谈论“仅包含没有库存的订单商品的订单”是没有意义的。 “订单”(我认为)代表客户订购的所有内容的完整列表; 如果您正在过滤该列表,那么您不再处理订单本身,而是处理过滤后的订单项列表。

我认为问题在于您是否真的想将订单视为 聚合根,或者您是否也希望能够从数据访问层中提取任意的 OrderItems 列表。

您说过在项目从数据库返回后对其进行过滤成本太高,但除非您对每个订单平均数百或数千个 OrderItems(或者有其他特别需要处理大量 OrderItems 的事情),否则您可能会过早地尝试优化并使事情变得比需要的更加困难。 我认为如果您可以将 Order 作为域逻辑中的聚合根和过滤器,那么您的模型将更易于使用。

如果情况确实并非如此,并且您需要在数据库中进行过滤,那么您可能需要考虑拥有一个单独的 OrderItem 存储库,该存储库将提供诸如“给我该订单的所有 OrderItems没存货”。 然后,您可以将它们作为 IList(或 IEnumerable)返回,因为它们不是完整的订单,但是而是一些经过过滤的 OrderItems 集合。

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.

一个人的旅程 2024-07-20 16:42:56

在服务层。

In the service layer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文