【问题标题】:When working with domain models and POCO classes, where do queries go?使用域模型和 POCO 类时,查询在哪里?
【发布时间】:2009-02-06 18:48:10
【问题描述】:

我是域模型、POCO 和 DDD 的新手,所以我仍在努力思考一些想法。

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

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

【问题讨论】:

    标签: language-agnostic domain-driven-design ddd-repositories


    【解决方案1】:

    实体是域的“单位”。存储库和服务引用它们,反之则不然。想一想:您是否将 DMV 放在口袋里?

    OrderItem 不是聚合根;它不应该通过存储库访问。它的身份是 Order 的本地标识,这意味着在谈论 OrderItems 时,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 来完成脏活。

    【讨论】:

    • 我确定我遗漏了一些东西,但是每当我尝试实现这样的东西时,它与“任何”LINQ 提供程序一起工作。例如,如果我使用 SDS、EF 或 L2S,GetCheapestOrderItem 中的查询可能“看起来”不同,尤其是当我必须执行连接或其他非平凡查询逻辑时。
    • 这是对泄漏抽象的普遍担忧。这些查询侧重于模型的语义。由 IQueryable 实现将这些语义转换为机制(“看起来不同”的部分)。
    【解决方案2】:

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

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

    这是 Martin Fowler 对 Repository Pattern. 的定义

    【讨论】:

    • 所以在我的 Order 类中我会做类似 _orderRepository.GetOrderItemsNotInStock(this)?
    • 不,你会有一个 OrderRepository 类,它有一个像 GetById(OrderId) 这样的函数。存储库存在于 Order 类之外,理想情况下,order 类非常小,可能只是带有 getter 和 setter 的属性,以及相关的逻辑
    • 相关逻辑->相关业务逻辑。虽然我更喜欢创建实际执行大部分业务逻辑的服务类,但我对服务类是否是 DDD 的一部分有点模糊。
    • GetById(OrderId) 会让我填充 Order 类,但是没有库存的订单商品怎么办?它们是 Order 聚合根的一部分,所以在某种程度上我认为 Orders 应该知道什么构成了缺货商品,但同时我的存储库知道如何查询它们……我很困惑
    • 添加到我的评论中,如果一个存储库知道什么是缺货项目,因此它可以查询它,这不是破坏 SoC 吗?我认为 Order 应该知道这一点,但同时它无法告诉如何将其转换为实际的优化查询,所以我不知道
    【解决方案3】:

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

    【讨论】:

    • 您是否会将这个存储库注入到 Order 类中(某行具有 IOrderRepository 作为 Order 构造函数的一部分),还是将它放在外面?如果是这样,你会在 Order 类中有什么?我不确定缺货商品的知识/商业规则在哪里
    • 您希望将存储库传递给任何需要订单的域对象。我认为每个 Order 实例都持有对其原始存储库的引用没有多大意义。
    • @Morendil 我半同意:一个订单不应该知道任何关于它的存储库,但除此之外 no 域对象应该知道 any 存储库.如果 DomainObjectA 需要 DomainObjectB 的实例,则一些更高级别的协调员应该与他们的存储库对话并连接起来。
    【解决方案4】:

    我认为谈论“仅包含无库存的 OrderItems 的订单”是没有意义的。 “订单”(我想)代表客户订购的任何东西的完整列表;如果您正在过滤该列表,则您不再处理 Order 本身,您正在处理的是经过过滤的 OrderItem 列表。

    我认为问题在于您是否真的想将 Orders 视为 Aggregate Root,或者您是否也希望能够从数据访问层中提取任意 OrderItem 列表。

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

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

    【讨论】:

      【解决方案5】:

      在服务层。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-01
        • 1970-01-01
        • 2010-10-28
        • 2016-02-09
        • 2010-11-09
        相关资源
        最近更新 更多