规格模式和DDD

时间:2013-08-12 11:22:30

标签: design-patterns nosql repository domain-driven-design polyglot-persistance

在了解有关DDD模式的个人游戏项目的上下文中,我错过了我的过滤器的Specification对象。

查看示例,似乎所有内容(如LinQ)都面向SQL数据库。但是对于许多NoSQL数据库而言,大多数查询,即使只是“select * from table”也需要预定义的视图。尽管如此,如果存储库正在映射Web服务,那么查询类型也会更加严格。

考虑到非SQL数据库的限制,是否存在规范模式的变体?我觉得这需要使用继承和静态声明来支持不同类型的持久性后端。

如何在我的存储库中组合“排序”和“过滤”?作为示例,请考虑存储库以获取订单项目列表。

(Query)findAllSortedByDate;
(Query)findAllSortedByName;
(Query)findAllSortedByQuantity;

因此,当表格显示时,这些是不同类型的排序。由于我可能会处理大量结果,因此我从不考虑在视图或视图模型中进行排序或过滤。最初我想到了一个 Proyection 类,它根据用户操作从存储库中选择正确的查询。但是,如果我想在不同的过滤器中组合不同的排序策略,这种方法效果不佳。

显然我需要某种类型的“规范”对象,但我不确定是否:

  1. 我应该将它们用于我的存储库,让它们变得更聪明吗?或者我应该将它们用于我的视图模型
  2. 如何正确限制我的规范对象以获得良好的多语言持久性?
  3. 最初我考虑使用存储库作为collection-like interface执行任何查询,但现在我注意到视图模型也可能表现为“有状态” “类似集合的界面,而前者是”无状态“集合式界面。

    1. 总的来说,我应该尝试在我的存储库中保留任何类型的排序/过滤吗?如果所有查询结果都可以加载到内存中,那么这样做可能会增加不必要的复杂性。
    2. 更新: 为了更好地解决这个问题,还要考虑尽管NoSQL视图可以被过滤和排序,但是全文搜索可能需要外部索引引擎,例如Lucene或SQLite-FTS,它们只为必须排序的查询提供实体的唯一标识。并再次过滤。

2 个答案:

答案 0 :(得分:5)

过滤

通过“类似集合的界面”,Fowler并不意味着公开类似于数组或列表的API:例如,它与ICollection<T>无关!存储库应encapsulate持久层的所有技术细节,但应定义其API以使其在业务域中具有表现力。

您应该将规范视为在域中相关的逻辑谓词(实际上它们是域模型中的一等公民),可以组成检查实体的不同质量从集合中选择实体(可以是存储库或简单列表的集合)。例如,在我为意大利银行设计的金融域模型中,我有DurationOfMBondSpecificationStandardAndPoorsLongTermRatingSpecification等等。

实际上,在DDD中,规范来自业务要求(通常是合同边界),必须由软件在其运营期间强制执行。他们可以用作过滤器的抽象,但这更像是一个幸运的副作用。

关于分类

大多数时候排序(和切片,分组......)只是一个表示问题。当这是一个业务问题时,应该从域专家的知识中提取适当的比较器(和石斑鱼等)作为域概念。 然而,即使只是一个表示问题,在存储库中处理它也要高效得多。

在.NET上,一个可能(并且非常昂贵)解决这些问题的方法是编写一个自定义LINQ提供程序(或多个),它可以转换所有可以用无处不在的表达的查询语言到所需的持久层。然而,这个解决方案有一个主要的缺点,如果你不能从开头翻译所有查询,你永远无法估计使用域更改应用程序的工作量:时间将会当你必须深入重构QueryProvider来处理一个新的复杂查询时(并且这样的重构将花费你比你能承受的更多)。

为解决这个问题,在(正在进行中且非常雄心勃勃的)Epic框架(免责声明:我是核心开发人员)中,我们选择加入规范模式和查询对象模式,提供通用API,使客户端能够使用规范进行过滤,使用比较器进行排序并使用整数进行切片,而不会产生LINQ的(不可预测的)成本。

在Fowler方案(下文)中,我们将规范(aCriteria)和辅助排序要求传递给存储库:

enter image description here

作为替代方案,您可以使用custom repositories:如果您没有数千种不同类型的查询,那么这是一种更便宜的方法。

奖金解决方案

快速但仍然正确的解决方案是“仅”使用您的持久性语言作为查询。 DDD用于复杂的操作边界和查询(大多数情况下)不起作用:因此您可以简单地使用SQL或NoSQL数据库提供的语言来检索您需要的投影和/或实体的identifiers需要操纵。您将看到您查询的数据集与确保域不变量所需的数据集有很大不同!

这就是为什么,例如,有时候,序列化文件可能是解决当前问题的域持久性的最佳方法。
毕竟,什么是文件系统,如果不是最分散的NoSQL数据库! :-D

答案 1 :(得分:1)

在DDD中,规范是适用于域对象的谓词。它涵盖了“过滤”需求,但不包括“排序”需求,因为排序不使用域对象的布尔函数,而是使用属性选择和排序方向。

当我需要过滤和排序时,我通常会写一个存储库方法:

findAll(Specification<Order> specification, 
        SortingOptions<Order> sortingOptions)

此时,我们根本不需要考虑底层的持久性机制。您的域层存储库接口不应由您的数据存储而是由域需求决定。

如果您的数据源难以创建过滤和/或排序的查询,那么您可以选择在内存中对查询结果中的对象进行过滤/排序。但是,不是在视图模型中这样做,而是IMO更好的创建存储库的具体实现,它将在内存中加载对象并执行排序/过滤在那里经营。

所有未来的其他表示层都将从中受益,如果结果是数据源(无论是NoSQL数据库,Web服务......)修复它的缺陷,调整存储库实现会更加方便。随着时间的推移。