que*_*en3 13 c# domain-driven-design
假设我有
public class Product: Entity
{
public IList<Item> Items { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
假设我想找到一个带有最大值的项...我可以添加方法Product.GetMaxItemSmth()并使用Linq(from i in Items select i.smth).Max())或手动循环或其他任何方法.现在,问题是这会将完整的集合加载到内存中.
正确的解决方案是执行特定的数据库查询,但域实体无法访问存储库,对吧?所以我要么
productRepository.GetMaxItemSmth(product)
Run Code Online (Sandbox Code Playgroud)
(这很丑陋,没有?),或者即使实体有权访问存储库,我也使用IProductRepository实体
product.GetMaxItemSmth() { return Service.GetRepository<IProductRepository>().GetMaxItemSmth(); }
Run Code Online (Sandbox Code Playgroud)
这也是丑陋的,是代码的重复.我甚至可以去看看并进行扩展
public static IList<Item> GetMaxItemSmth(this Product product)
{
return Service.GetRepository<IProductRepository>().GetMaxItemSmth();
}
Run Code Online (Sandbox Code Playgroud)
哪个更好只是因为它并没有真正使存储库中的实体混乱......但仍然是方法重复.
现在,这是是否使用product.GetMaxItemSmth()或productRepository.GetMaxItemSmth(product)再次使用的问题.我错过了DDD的东西吗?这里的正确方法是什么?刚用productRepository.GetMaxItemSmth(product)?这是每个人都使用和满意的吗?
我只是感觉不对...如果我无法Items从产品本身访问产品,为什么我需要这个系列呢Product?然后,Product如果它不能使用特定的查询并访问其集合而没有性能命中,可以做任何有用的事情吗?
当然,我可以使用效率较低的方式,而且从不介意,当它很慢时,我会将存储库调用作为优化注入实体......但即使这听起来也不对,是吗?
有一点要提一下,也许它不是DDD ......但我需要在Product中使用IList才能获得使用Fluent NHibernate生成的数据库模式.不过,请在纯DDD环境中自由回答.
更新:这里描述了一个非常有趣的选项:http://devlicio.us/blogs/billy_mccafferty/archive/2007/12/03/custom-collections-with-nhibernate-part-i-the-basics.aspx,不仅处理与DB相关的集合查询,也可以帮助进行集合访问控制.
我相信在 DDD 方面,每当您遇到这样的问题时,您应该首先问问自己您的实体是否设计正确。
如果您说 Product 有一个 Items 列表。您是说 Items 是 Product 聚合的一部分。这意味着,如果您对产品执行数据更改,您也在更改项目。在这种情况下,您的产品及其项目需要在事务上保持一致。这意味着对一个或另一个的更改应该始终在整个 Product 聚合上级联,并且更改应该是 ATOMIC。这意味着,如果您更改了产品名称和其中一个项目的名称,并且项目名称的数据库提交有效,但产品名称失败,则项目名称应回滚。
这是聚合应该代表一致性边界而不是组合便利性的事实。
如果在您的域中要求项目更改和产品更改在事务上保持一致是没有意义的,那么产品不应持有对项目的引用。
您仍然可以建模 Product 和 items 之间的关系,只是不应该有直接引用。相反,您希望有一个间接引用,即 Product 将有一个 Item Id 列表。
在直接引用和间接引用之间进行选择应首先基于事务一致性问题。一旦您回答了这个问题,如果您似乎需要事务一致性,那么您必须进一步询问它是否会导致可伸缩性和性能问题。
如果您有太多产品用于太多产品,这可能会扩展并表现不佳。在这种情况下,您应该考虑最终一致性。这是当您仍然只有从 Product 到 items 的间接引用时,但通过其他一些机制,您可以保证在未来的某个时间点(希望尽快),Product 和 Items 将处于一致状态。示例是,随着项目余额的更改,产品总余额增加,而每个项目都被一个一个更改,产品可能不完全具有正确的总余额,但是一旦所有项目都完成更改,产品将自行更新以反映新的总余额,从而返回到一致状态。
最后一个选择更难做出,您必须确定是否可以接受最终一致性以避免可扩展性和性能问题,或者成本是否太高而您宁愿拥有事务一致性并接受可扩展性和性能问题。
现在,一旦您间接引用了 Items,您将如何执行 GetMaxItemSmth()?
在这种情况下,我认为最好的方法是使用双重调度模式。您创建一个 ItemProcessor 类:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
它是对应的界面:
public interface IItemProcessor
{
Item GetMaxItemSmth(Product product);
}
Run Code Online (Sandbox Code Playgroud)
它将负责执行您需要的逻辑,涉及使用您的产品数据和其他相关实体数据。或者,这可以承载任何一种跨越多个实体的复杂逻辑,并且每个实体都不太适合,因为它需要跨越多个实体的数据。
然后,在您的 Product 实体上添加:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
注意:如果您只需要查询 Max 项并取回值,而不是实体,则应完全绕过此方法。创建一个具有 GetMaxItemSmth 的 IFinder,它返回您的专用读取模型。可以有一个单独的模型仅用于查询,以及一组执行专门查询以检索此类专门读取模型的 Finder 类。您必须记住,聚合仅用于数据更改的目的。存储库仅适用于聚合。因此,如果没有数据更改,则不需要聚合或存储库。
拥有一个Items集合并拥有GetXXX()方法都是正确的.
为了纯粹,您的实体不应该直接访问存储库.但是,它们可以通过查询规范进行间接引用.查看Eric Evans的书第229页.像这样的东西:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
如何获得对存储库的引用是您的选择(DI,服务定位器等).虽然这消除了Entity和Respository之间的直接引用,但它并没有减少LoC.
一般来说,如果我知道GetXXX()方法的数量将来会引起问题,我只会提前介绍它.否则,我将把它留给将来的重构练习.