Jana(软件架构师)发言:
仓储(Repository)模式使用类似集合的接口在领域和数据映射层之间进行转换,以访问领域对象。有关更多信息,请参考Martin Fowler,Catalog of Patterns of Enterprise Application Architecture,Repository。
Contoso的团队评估了实现ViewRepository类的两种方法:使用IQueryable接口和使用非通用的数据访问对象(DAOs)。
使用IQueryable接口ViewRepository类考虑的一种方法是让它返回一个IQueryable实例,该实例允许客户端使用LINQ来指定其查询。返回IQueryable实例很简单,很多ORM框架都可以,例如Entity Framework或NHibernate,下面的代码片段演示了客户端如何做此类查询。
var ordersummary = repository.Query<OrderSummary>().Where(LINQ query to retrieve order summary); var orderdetails = repository.Query<OrderDetails>().Where(LINQ query to retrieve order details);这种方法有几个优点:
简单
这种方法在底层数据库上使用一个薄的抽象层。许多ORM都支持这种方法,它将您必须编写的代码量降到最低。
您只需要定义一个仓储和一个查询方法。
您不需要单独的查询对象。在读端,查询应该很简单,因为您已经对写端数据进行了反规范化,以支持读端。
可以使用LINQ在客户端上提供对过滤、分页和排序等特性的支持。
可测试性
您可以使用LINQ to object进行Mocking。
Markus(软件开发人员)发言:
在参考实现(RI)中,我们使用Entity Framework,我们根本不需要编写任何代码来获取IQueryable实例。我们也只有一个ViewRepository类。
可能有人反对这个方法,包括:
把数据存储层替换为非关系型数据库将很不容易,因为需要提供IQueryable实例。但无论如何,您总是可以为不同的限界上下文选择使用适合的,不同的读取端实现方式。
客户端在执行操作的时候可能会滥用IQueryable接口,您应该确保非规范化的数据完全满足客户的需求。
使用IQueryable接口隐藏了查询办法。但是,由于在写端对数据进行过反规范化,因此对关系数据库表的查询没办法做更复杂的查询。
很难知道您的集成测试是否覆盖了查询方法的所有不同用途。
使用非通用DAOs另一种方法是让ViewRepository暴露出一个Find方法和一个Get方法,如下面的代码片段所示。
var ordersummary = dao.FindAllSummarizedOrders(userId); var orderdetails = dao.GetOrderDetails(orderId);您还可以选择使用不同的DAO类。这将使访问不同数据源变得更容易。
var ordersummary = OrderSummaryDAO.FindAll(userId); var orderdetails = OrderDetailsDAO.Get(orderId);这种方法有几个优点:
简单
对客户端来说,依赖关系更加清晰。例如,客户端引用一个显式的IOrderSummaryDAO实例,而不是一个通用的IViewRepository实例。
对于大多数查询,只有一到两种预定义的访问对象的方法。不同的查询通常返回不同的投射。
灵活性
Get和Find方法隐藏了数据存储分区的细节,还隐藏了使用ORM或显式执行SQL代码等数据访问方法。这使得将来更容易改变这些选择。
Get和Find方法可以使用ORM、LINQ和IQueryable接口在背后从数据存储中获取数据。这是一个选择,您可以建立在一个方法接一个方法的基础上。
性能
您可以轻松地优化Find和Get方法运行的查询。数据访问层执行所有查询。客户端没有任何风险试图去做复杂的效率低的查询。
可测试性
为Find和Get方法创建单元测试要比为客户端所有可能的LINQ查询范围创建合适的单元测试更容易。
可维护性
所有查询都定义在相同的位置DAO类中,从而更容易一致地修改系统。
对这个方法可能的反对意见包括:
使用IQueryable接口可以更容易地在UI中支持分页、过滤和排序等功能。无论如何,如果开发人员意识到这一缺点并尽力交付基于任务的UI,那么这应该不是问题。
把部分已完成的订单信息提供给读取端UI层通过在读取端查询模型获得的订单数据来显示。UI显示给注册者的部分数据是关于部分已完成订单的信息:订单中的每种座位类型,请求的座位数量和可用的座位数量。这是系统仅在注册者使用UI创建订单时使用的临时数据。企业只需要存储关于实际购买座位的信息,而不需要存储注册者请求的座位和注册者购买的座位之间的差异。
这样做的结果是,关于注册者请求多少座位的信息只需要存在于读取端模型中。