要存储库还是不存储库


10

当我第一次了解域驱动设计时,我还被介绍到了存储库和工作单元模式,这些存储模式和工作单元曾经看起来像是那些酷炫的孩子向数据库发送SQL查询(例如穴居人)的最佳选择。我对这个话题的了解越深,我越了解到它们似乎不再是必需的了,因为EFNHibernate之类的ORM将工作单元和存储库都实现到一个称为会话或上下文的API中。

现在我不确定该怎么办。是否存储库。我真的很理解这样的论点,即这样的泄漏抽象只会使事情复杂化,而绝对没有增加任何可以简化数据访问的内容,但是,将我的应用程序的各个方面都耦合到例如Entity Framework的感觉并不正确。通常,我遵循一些简单的准则:

  1. 域层是系统的核心,包含实体,服务,存储库...
  2. 基础结构层提供基础结构领域的域接口的实现,例如文件,数据库,协议。
  3. 应用程序层承载一个组合根,该根将所有事物连接起来并进行编排。

我的解决方案通常如下所示:

Domain.Module1
Domain.Module2
    IModule2Repo
    IModule2Service
    Module2
Infrastructure.Persistence
    Repositories
        EntityFrameworkRepositoryBase
MyApp
    Boostrapper
        -> inject EntityFrameworkRepositoryBase into IRepository etc.

我使用a保持域层整洁,IRepository<'T>这也是一个域问题,不依赖于任何其他告诉我如何访问数据的内容。当我现在要实现IModule2Service需要数据访问的具体实现时,我将不得不注入DbContext并将其直接耦合到基础架构层。(要进入Visual Studio项目,由于循环依赖关系,最终可能会非常棘手!

另外有什么可以到其他托管作品fucktons?CQRS?如何抽象一个纯粹的基础架构?


1
顺便说一句,如果您正在谈论DDD中描述的“存储库”,那么EF和nHibernate都不是存储库。当然,他们部分抽象了数据访问的机制,但是存储库的功能远不止于此
埃里克·金

Answers:


4

工程就是妥协。软件开发也是如此。现在,我相信唯一更简单的选择是直接使用ORM。但是就像您说的那样,这可能会将您锁定在特定的持久性框架中。

因此,您必须问自己“额外的复杂性是否值得将代码与持久性脱钩”。每当我听到人们说他们想使代码与持久性脱钩时,我会问:“您在职业生涯中改变了多少次持久性框架?”

我在存储库中看到的问题是,它们使共同的更改变得更加困难。例如。添加了查询聚合的新方法。而且,它们使不常见的更改(据说)更容易。例如。更改存储库的实现。

然后还有一个单元测试参数,我说“如果持久性框架不允许您在内存或本地模拟数据库,那么根本就不值得使用该框架。”


1
存储库的目的是使应用程序与持久性脱钩,并且不涉及复杂性。而且我至少更改了一次持久性详细信息,因为数据库访问是我编写的最后一件事,直到那一点我才在内存存储库中使用。此外,当您直接使用持久性详细信息时,会有一个非常微妙的陷阱。您的业​​务对象将倾向于与之兼容,而不是不可知论的。这是因为如果没有(某些难看的)解决方法,就无法从任何存储(内存)中直接还原丰富,正确封装的实体
MikeSW 2014年

关于添加一种查询聚合或任何实体的新方法,这就是CQRS出现的原因。就个人而言,我保留存储库用于域目的,并使用查询处理程序进行实际查询。这些处理程序非常紧密地耦合到db和ofc,它们的工作效率很高。
MikeSW 2014年

3

我是专业存储库,尽管我已经脱离了通用存储库模式。相反,我将我的存储库与它们服务的业务功能对齐。存储库的目的不是抽象化ORM,因为这不是我希望更改的任何内容,同时,我避免使存储库过于精细。(即CRUD)相反,我的存储库具有两到三个主要目的:

  • 资料检索
  • 资料建立
  • 硬删除

对于数据检索,存储库始终返回IQueryable<TEntity>。对于数据创建,它返回TEntity。该存储库处理我的基本级别的过滤,例如使用软删除模式的系统的授权“活动”状态,以及使用历史数据的系统的“当前”状态。数据创建仅负责确保解析和关联了所需的引用,并确保实体已设置好并准备就绪。

数据更新是与所讨论实体合作的业务逻辑的职责。这可以包括验证规则之类的内容。我不会尝试将其封装在存储库方法中。

我的大多数系统中的删除都是软删除,因此将属于数据更新。(IsActive =假)对于硬删除,这将是存储库中的单行。

为什么要存放库?测试能力。当然,可以模拟DbContext,但是模拟返回的类更简单IQueryable<TEntity>。它们还可以与UoW模式配合使用,我个人使用Mehdime的DbContextScope模式在所需级别上划分工作单元(MVC中的Ie控制器),并让我的控制器和助手服务类利用存储库而无需将引用传递给周围的UoW / dbContext。使用IQueryable意味着您在存储库中不需要很多包装方法,并且您的代码可以优化如何使用数据。例如,存储库不需要公开“ Exists”或“ Count”之类的方法,也可以在需要数据子集的情况下尝试将实体与其他POCO打包在一起。他们甚至不需要处理您可能需要或可能不需要的相关数据的紧急加载选项。通过传递IQueryable,调用代码可以:

.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()

非常灵活,通过测试PoV,我的模拟存储库仅需要返回已填充实体对象的列表。

至于通用存储库,我大部分时间都远离了这些存储库,因为当您最终为每个表创建一个存储库时,控制器/服务最终会引用多个存储库来执行一项业务操作。在大多数情况下,这些存储库中只有一个或两个实际上在执行写操作(前提是您正确使用了导航属性),而其他存储库则在支持Reads。与创建5个或6个不同的存储库相比,我宁愿拥有诸如OrdersRepository之类的东西,它能够读取和创建订单,并能够读取任何相关的查询等(轻量级客户对象,产品等)以供创建订单时参考。它可能会违反DNRY纯粹主义者,但是我的观点是,存储库的目的是为创建包含相关参考的订单提供服务。Repository<Product>要获得订单所需的产品,我只需要一个具有少数字段的实体即可。我的OrderRepository可能有一个.GetProducts()返回的方法IQueryable<ProductSummary>,比一个Repository<Product>最终返回的方法好,它最终有几种“获取”方法来尝试满足应用程序需求的不同领域,和/或提供一些复杂的传递过滤表达式。

我选择了更容易理解,测试和调整的简单代码。它可能以不良的方式被滥用,但我宁愿存在容易发现和纠正滥用的地方,而不是尝试以一种无法滥用,失败的方式“锁定”代码,然后再拥有一些东西。梦to以求地真正完成客户付费才能最终完成的任务。:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.