4

Within the context of a personal play project for learning more about DDD patterns, I am missing a Specification object for my filters.

Looking around for examples, it seems that everything (like LinQ) is oriented towards SQL databases. However with many NoSQL databases most queries, even just a "select * from table" requires a predefined view. Nevertheless, if the repository is mapping a web service even the type of queries is much more rigid.

Are there variations of the Specification pattern taking into account the limitations of non SQL databases? I have the feeling that this would require the use of inheritance and static declarations to support different type of persistence backends.

How should I combine "sorting" and "filtering" in my repositories? As an example consider the repository for a list of Order items.

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

So these are different type of sorting when displayed by a table. Since I might handle large results, I never considered sorting or filtering in my views or view models. Initially I thought about a Proyection class that selects the right query from a repository according the user actions. However this does not work well if I want to combine different sort strategies along different filters.

Obviously I need some type of "specification" object, but I am not sure if:

  1. Should I use them for my repositories, making them smarter? Or should I use them for my view models?
  2. How can properly restrict my specification objects for a good polyglot persistence?

Originally I thought about performing any query with a Repository acting as a collection-like interface but now I am noticing that a view model may also behave as "stateful" collection-like interface while the former are "stateless" collection-like interfaces.

  1. Overall should I try to keep any type of sorting/filtering inside my repositories? It seems that doing so, might add unnecessary complexity if all the query results can be loaded into memory.

UPDATE: To spice up this question, consider also that although NoSQL views can be filtered and sorted, a full text search might need an external indexing engine such as Lucene or SQLite-FTS providing only the unique identity of the Entities for a query that must be sorted and filtered again.

4

2 回答 2

5

关于过滤

通过“类集合接口”,Fowler 并不是指暴露类似于数组或列表的 API:ICollection<T>例如,它与 没有任何关系!Repository 应该封装持久层的所有技术细节,但应该定义它的 API,以便它在业务领域中具有表现力。

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

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

在排序

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

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

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

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

在此处输入图像描述

作为替代方案,您可以只使用自定义存储库:如果您没有数千种不同类型的查询,这是迄今为止更便宜的方法。

奖金解决方案

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

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

于 2013-08-13T09:27:53.337 回答
2

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

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

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

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

如果您的数据源难以创建过滤和/或排序的查询,您可以完美地选择之后在内存中的查询结果上过滤/排序对象。但是,与其在视图模型中这样做,IMO 最好创建一个存储库的具体实现,将对象加载到内存中并在那里执行排序/过滤操作。

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

于 2013-08-13T12:08:02.783 回答