规范模式和领域驱动设计(DDD)

4
在学习DDD模式的个人玩耍项目中,我缺少一个用于过滤器的规范对象。
寻找示例时,似乎所有内容(如LinQ)都面向SQL数据库。然而,对于许多NoSQL数据库,即使只是“从表中选择*”的查询也需要预定义视图。然而,如果存储库映射Web服务,甚至查询类型也更加严格。
是否有将非SQL数据库的限制考虑在内的规范模式变体?我觉得这需要使用继承和静态声明来支持不同类型的持久性后端。
我应该如何在我的存储库中组合“排序”和“过滤”?以订单项列表的存储库为例。
(Query)findAllSortedByDate;
(Query)findAllSortedByName;
(Query)findAllSortedByQuantity;

这些是表格显示时不同类型的排序方式。由于我可能处理大量结果,因此从未考虑在我的视图或视图模型中进行排序或过滤。最初,我考虑使用一个“Proyection”类,根据用户操作从存储库中选择正确的查询。但是,如果我想结合不同的排序策略和不同的过滤器,这种方法效果不佳。
显然,我需要某种类型的“规范”对象,但我不确定:
1. 我应该将它们用于我的存储库,使它们更加智能吗?还是应该将它们用于我的视图模型? 2. 如何为良好的多语言持久性正确限制我的规范对象?
最初,我考虑使用作为“类似集合接口”的{{link1:Repository}}执行任何查询,但现在我注意到,一个view model也可以作为“有状态”的类集合接口,而前者则是“无状态”的类集合接口。
  1. 总体来说,我是否应该尝试在我的代码库中保留任何类型的排序/过滤?如果所有查询结果都可以加载到内存中,这样做似乎会增加不必要的复杂性。

更新:为了让这个问题更有趣,考虑到虽然NoSQL视图可以被过滤和排序,但全文搜索可能需要一个外部索引引擎,例如Lucene或SQLite-FTS,仅提供用于对查询进行排序和过滤的实体的唯一标识。

2个回答

6

关于过滤

在Fowler的“类集合接口”中,他并不是指暴露类似于数组或列表的API:例如{{link1:ICollection<T>}}与此无关!一个仓储应该封装所有持久层技术细节,但其API应该被定义为在业务领域中具有表现力。

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

实际上,在DDD中,规约来自于业务需求(通常是合同限制),软件必须在其操作期间执行。它们可以用作过滤器的抽象,但这更像是一个幸运的副作用。

关于排序

大多数情况下,排序(以及分割和分组等)只是一个展示问题。当它是业务问题时,适当的比较器(以及分组器等)应该从领域专家的知识中提炼出来作为领域概念。不过,即使只是展示问题,也更有效率地在存储库中处理。

在.NET中,这些问题的一种可能的(且非常昂贵的)解决方案是编写自定义LINQ提供程序(或多个),该提供程序可以将使用普遍语言表达的所有查询转换为所需的持久化层。然而,这种解决方案有一个主要缺点,如果您无法从一开始就翻译所有查询,那么您永远无法估计使用域进行应用程序更改的工作量:到了某个时候,您将不得不深度重构QueryProvider以处理新的复杂查询(而这样的重构将花费您比您承受得起的多得多的代价)。

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

enter image description here

作为替代方案,您可以使用自定义存储库:如果您没有成千上万种不同类型的查询,这是远比较便宜的方法。 额外的解决方案 一个快速但仍然正确的解决方案是“只需”使用您的持久化语言来进行查询。 DDD 用于复杂的操作边界和查询(大多数情况下)并非操作性:因此,您可以简单地使用 SQL 或 NoSQL 数据库提供的语言来检索所需的投影和/或需要操作的实体的标识符。 您将看到,您查询的数据集与确保域不变式所需的数据集非常不同!
这就是为什么,例如,有时序列化文件可以成为处理问题的领域持久性的最佳方法。
毕竟,如果不是最广泛的 NoSQL 数据库,那么文件系统是什么呢! :-D

1
你好,规范模式应该返回哪种数据类型?IEnumerable、IQueryable还是Expression? - user11915641

3
在DDD中,规约是适用于领域对象的谓词。它涵盖了“过滤”需求,但不包括“排序”需求,因为排序不使用域对象的布尔函数,而是属性选择和排序方向。
当我需要过滤和排序时,通常编写以下存储库方法:
findAll(Specification<Order> specification, 
        SortingOptions<Order> sortingOptions)

在这点上,我们不需要考虑底层持久化机制。你的领域层仓储接口不应由数据存储方式定义,而应根据领域需求来设计。
如果你的数据源难以创建筛选和/或排序查询,你可以选择在查询结果内存中进行对象筛选/排序。但是,我认为最好创建一个具体的仓库实现,在其中加载对象并执行排序/筛选操作,而不是在“视图模型”中执行此操作。
所有未来新增的表示层都将从中受益,并且如果发现数据源(无论是NoSQL数据库、Web服务等)随时间推移修复了其缺陷,则调整存储库实现将更加方便。

1
嗨,规范模式应该返回什么数据类型?IEnumerable、IQueryable、Expression? - user11915641

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接