实现存储库模式的最佳方法?

Posted

技术标签:

【中文标题】实现存储库模式的最佳方法?【英文标题】:Best way to implement Repository Pattern? 【发布时间】:2010-11-27 08:58:34 【问题描述】:

我一直在探索 BDD/DDD,因此试图提出存储库模式的正确实现。到目前为止,很难就实现这一点的最佳方式达成共识。我试图将其归结为以下变体,但我不确定哪种方法最好。

作为参考,我正在构建一个以 NHibernate 作为后端的 ASP.MVC 应用程序。

public interface IRepository<T> 
        // 1) Thin facade over LINQ
        T GetById(int id);
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IQueryable<T> Find();
        // or possibly even
        T Get(Expression<Func<T, bool>> query);
        List<T> Find(Expression<Func<T, bool>> query);


public interface IRepository<T> 
        // 2) Custom methods for each query
        T GetById(int id);
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IList<T> FindAll();
        IList<T> FindBySku(string sku);
        IList<T> FindByName(string name);
        IList<T> FindByPrice(decimal price);
        // ... and so on


public interface IRepository<T> 
        // 3) Wrap NHibernate Criteria in Spec pattern
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IList<T> FindAll();
        IList<T> FindBySpec(ISpecification<T> specification);
        T GetById(int id);



public interface IRepository<T> 
        // 4) Expose NHibernate Criteria directly
        T GetById(int id);
        void Add(T entity);
        void Update(T entity);
        void Remove(T entity);
        IList<T> FindAll();
        IList<T> Find(ICriteria criteria);
        // .. or possibly
        IList<T> Find(HQL stuff);

我最初的想法是

1) 从效率的角度来看很好,但随着事情变得更加复杂,我可能会遇到麻烦。

2) 看起来很乏味,并且最终可能会导致一个非常拥挤的课程,但否则会在我喜欢的领域逻辑和数据层之间提供高度分离。

3) 预先编写查询似乎很困难,并且需要做更多的工作来编写查询,但将交叉污染限制在 Specs 层。

4) 我最不喜欢,但可能是最直接的实现,并且对于复杂的查询可能是最高效的数据库,尽管它给调用代码带来了很多责任。

【问题讨论】:

+1 及时提问。我也一直在为存储库模式而苦苦挣扎。 可能不是您想要的,但我发现 Spec 模式的这种实现非常酷:mathgeekcoder.blogspot.com/2008/07/…。主要是因为它可以让你使用 Linq2Sql/Linq2NHibernate/etc。 感谢 TrueWill 和 Martinho,这实际上与我最近发布的另一个主题非常相关 ***.com/questions/1408553/… 【参考方案1】:

对于“以上都不是”方法也有很好的论据。

通用存储库的问题在于,您假设系统中的所有对象都支持所有四种 CRUD 操作:创建、读取、更新、删除。但在复杂系统中,您可能会拥有仅支持少数操作的对象。例如,您可能有只读对象,或者创建但从未更新的对象。

您可以将 IRepository 接口分解为小的接口,用于读取、删除等,但这很快就会变得混乱。

Gregory Young 提出了一个很好的论点(从 DDD / 软件分层的角度来看),每个存储库都应该只支持特定于您正在使用的域对象或聚合的操作。这是他在generic repositories上的文章。

对于另一种观点,请参阅此 Ayende blog post。

【讨论】:

我同意。我只想补充一点,这并不意味着你不能有一个通用的实现,你只是不应该有一个通用的接口。您可以拥有一个抽象 Repository (并且没有 IRepository 接口),所有方法都受保护。然后,每个特定的存储库都可以对其进行扩展,并将所需的方法公开以实现 ISpecificRepository 接口。 确实!以我的经验,实现存储库模式的最佳方式是实现它。相反,使用良好的 ORM API(如 Java 中的 JPA)和单个“ApplicationDatabase”类使其更易于使用。无论如何,这是我对 Java EE 应用程序的选择;我发现它使应用程序代码比拥有一堆额外的“EntityAbcRepository”类更简单、更短、更有凝聚力。【参考方案2】:

我认为它们都是不错的选择(如果您不想将自己束缚在休眠状态,可能除了 4 个),而且您似乎已经充分分析了利弊,以便根据您当前的努力自行做出决定。 不要在这方面打败自己。

我猜我目前正在研究 2 和 3 之间的混合物:

public interface IRepository<T> 

        ...
        IList<T> FindAll();
        IList<T> FindBySpec(ISpecification<T> specification);
        T GetById(int id);


public interface ISpecificRepository : IRepository<Specific> 

        ...
        IList<Specific> FindBySku(string sku);
        IList<Specific> FindByName(string name);
        IList<Specific> FindByPrice(decimal price);

还有一个 Repository (of T) 基类。

【讨论】:

我怀疑#2 的一点点会渗透到每个解决方案中。【参考方案3】:

我们正在做的一件事是我们所有的存储库都有不同的需求,因此我们正在创建一个接口集合:

public interface IReadOnlyRepository<T,V>

   V Find(T);

在此示例中,只读存储库只是从数据库中获取。 T,V 的原因是 V 代表存储库返回的内容,而 T 代表传入的内容,因此您可以执行以下操作:

public class CustomerRepository:IReadOnlyRepository<int, Customer>, IReadOnlyRepository<string, Customer>

    public Customer Find(int customerId)
    
    

    public Customer Find(string customerName)
    
    

我还可以为添加、更新和删除创建单独的界面。这样,如果我的存储库不需要该行为,那么它就不会实现接口。

【讨论】:

这很有趣。我需要考虑使用 IReadonlyRepository 的潜在好处,但乍一看,我有点喜欢它。感谢分享。【参考方案4】:

我是 1 的爱好者,因为我可以创建过滤器和分页扩展方法,而不是应用于 Find 方法的 IQueryable 返回值。我将扩展方法保留在数据层中,然后在业务层中动态构建。 (诚​​然,并非完全纯粹。)

当然,当系统稳定后,我可以选择使用相同的扩展方法创建特定的 Find 方法并使用 Func 进行优化。

【讨论】:

【参考方案5】:

将 NH 与 Linq 结合使用时,您的存储库可以是:

session.Linq<Entity>()

规范是处理的事情:

IQueryable<Entity>

如果你愿意,你可以把它全部隐藏起来,但要抽象一个抽象是很多平凡的工作。

简单就是好的。是的,NH 有数据库,但它提供了更多的模式。除了 DAL 之外,还依赖于 NH 远非罪过。

【讨论】:

但是直接在逻辑中使用会话,虽然简单,但会违反控制反转,并且很难测试,不是吗? 取决于您如何构建代码。如果您使用工作单元模式,那么 ISession 就是您的 UoW。如果这会使事情变得更好(我没有),您可以在 ISession 上放置一个薄的外观。所以你就剩下存储库了。我认为存储库是非常基本的——获取对象、保存对象、将对象公开为一个集合(ISession 完成了所有这些)。查询/规范可以由 IQueryable 处理。如果您的域逻辑最终依赖于 ISession,那么它将最终依赖于存储库。这两种情况同样糟糕。 所以 ISession 最终可能会成为一种“无所不能”的界面。这就是为什么将它放在另一个接口后面可以更明确地说明它的使用方式(IUnitOfWork、IRepository 等)。我认为这部分是个人喜好,我不会花很多时间来设计完美的抽象。【参考方案6】:

我相信这取决于您的需求。将存储库与您考虑使用的其他设计模式结合起来考虑是很重要的。最后,这很大程度上取决于您对存储库的期望(使用它的主要原因是什么)。

您是否需要创建严格的层(例如您将来需要将 NHibernate 替换为 Entity Framework)?您想专门为存储库方法编写测试吗?

没有最好的方法来创建存储库。只有几种方法,这完全取决于您,哪种方法最适合您的需求。

【讨论】:

【参考方案7】:

仓库枯萎

LosTechies 的 Jimmy Bogard 的相关文章

http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/09/10/wither-the-repository.aspx

另外,另一篇包含一些 cmets 建议版本 #2 的快速文章实际上是 DOA 模式,而不是存储库。

http://fabiomaulo.blogspot.com/2009/06/linq-and-repository.html

【讨论】:

以上是关于实现存储库模式的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

使用 ServiceStack.ORMLite 实现工作单元和存储库模式的最佳实践

存储库模式的最佳实践?

存储库模式的好处和 Spring 实现

大数据处理方法的最佳设计模式

在存储库级别在 Doctrine2 ORM 中实现分页的最佳实践

处理本地存储库中旧快照的最佳方法?