严格来说,存储库提供了用于获取/放置域对象的集合语义。它围绕您的物化实现(ORM、手动、模拟)提供了一个抽象,以便域对象的消费者与这些细节分离。在实践中,存储库通常抽象对实体的访问,即具有身份的域对象,并且通常具有持久的生命周期(在 DDD 风格中,存储库提供对聚合根的访问)。
存储库的最小接口如下:
void Add(T entity);
void Remove(T entity);
T GetById(object id);
IEnumerable<T> Find(Specification spec);
尽管您会看到命名差异和 Save/SaveOrUpdate 语义的添加,但以上是“纯粹”的想法。您将获得 ICollection 添加/删除成员以及一些查找器。如果您不使用 IQueryable,您还会在存储库中看到 finder 方法,例如:
FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();
在这种情况下使用 IQueryable 有两个相关的问题。第一个是以域对象关系的形式向客户端泄露实现细节的可能性,即违反德墨忒耳定律。第二个是存储库获得了可能不属于域对象存储库本身的查找职责,例如,查找关于所请求的域对象而不是相关数据的投影。
此外,使用 IQueryable 会“打破”模式:具有 IQueryable 的存储库可能会也可能不会提供对“域对象”的访问。IQueryable 为客户提供了很多关于最终执行查询时将实现什么的选项。这是关于使用 IQueryable 的争论的主要内容。
关于标量值,您不应该使用存储库来返回标量值。如果您需要一个标量,您通常会从实体本身获得它。如果这听起来效率低下,是的,但您可能不会注意到,具体取决于您的负载特性/要求。如果出于性能原因或需要合并来自许多域对象的数据,您需要域对象的备用视图,您有两种选择。
1)使用实体的存储库来查找指定的实体并将项目/映射到平面视图。
2)创建一个查找器接口,专门用于返回一个封装了您需要的扁平视图的新域类型。这不会是一个存储库,因为没有集合语义,但它可能会使用现有的存储库。
如果您使用“纯”存储库来访问持久化实体,需要考虑的一件事是,您会损害 ORM 的一些好处。在“纯”实现中,客户端无法提供如何使用域对象的上下文,因此您无法告诉存储库:“嘿,我只是要更改 customer.Name 属性,所以不要“不用费心去获取那些急切加载的参考资料。” 另一方面,问题是客户是否应该知道这些东西。这是一把双刃剑。
就使用 IQueryable 而言,大多数人似乎都乐于“打破”模式以获得动态查询组合的好处,尤其是对于分页/排序等客户端职责。在这种情况下,您可能有:
Add(T entity);
Remove(T entity);
T GetById(object id);
IQueryable<T> Find();
然后您可以取消所有这些自定义 Finder 方法,这些方法会随着查询需求的增长而使存储库变得混乱。