域驱动设计中的规范模式 [英] Specification Pattern in Domain Driven Design

查看:98
本文介绍了域驱动设计中的规范模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在努力解决与DDD相关的规范问题,并且已经阅读了很多有关DDD以及规范和存储库的信息。

I've been struggling around an DDD related issue with Specifications and I've read much into DDD and specifications and repositories.

但是,如果尝试在不破坏域驱动设计的情况下将所有这三个方法结合起来,则会出现问题。归结为如何考虑性能而应用过滤器。

However, there is an issue if trying to combine all 3 of these without breaking the domain driven design. It boils down to how to apply filters with performance in mind.

首先有几个明显的事实:

First a few obvious facts:


  1. 获得DataAccess / Infrastructure层的存储库

  2. 域模型表示业务逻辑并转到域层

  3. 数据访问模型表示持久性层并转到Persistance / Infrastructure / DataAccess层

  4. 业务逻辑进入域层

  5. 规范是业务逻辑,因此它们也属于域层。

  6. 在所有这些示例中,存储库中都使用了ORM框架和SQL Server

  7. 持久性模型可能不会泄漏到域层中

  1. Repositories to got DataAccess/Infrastructure Layer
  2. Domain Models represent Business Logic and go to the Domain layer
  3. Data Access Models represent Persistence layer and go to the Persistance/Infrastructure/DataAccess layer
  4. Business Logic goes to Domain Layer
  5. Specifications are Business Logic, so they belong to Domain layer too.
  6. In all these examples, an ORM Framework and SQL Server is used inside the Repository
  7. Persistance Models may not leak into Domain Layer

到目前为止,如此简单。当/如果我们尝试将规范应用于存储库而不破坏DDD模式或出现性能问题,则会出现问题。

So far, so easy. The problem arises when/if we try to apply Specifications to the Repository and not breaking DDD pattern or having performance issues.

应用规范的可能方法:

1)经典方法:在域中使用域模型进行规范域层

应用传统的规范模式,并使用 IsSatisfiedBy 方法,返回 bool 和组合规格来组合多个规格。

Apply the traditional Specification Pattern, with a IsSatisfiedBy method, returning a bool and Composite Specifications to combine multiple Specifications.

这让我们将规范保留在域层中,但是...

This let us keep specifications in Domain Layer, but...


  1. 它必须与域模型一起使用,而存储库使用持久性模型表示持久性层的数据结构。使用 AutoMapper 这样的映射器可以轻松解决此问题。

  2. 但是,无法解决的问题:所有规范都必须在内存中执行。在大型表/数据库中,如果您只需要遍历所有实体以过滤出符合您要求的实体,这将产生巨大的影响

  1. It has to work with Domain Models, while the repository uses Persistence Models which represent the data structure of the persistence layer. This one is easy to fix with usage of mappers such as AutoMapper.
  2. However, the problem which can't be solved: All the specifications would have to be performed in memory. In a big table/database this means a huge impact if you have to iterate through ALL Entities only to filter out the one which meet your specifications

2)使用持久性模型的规范

这类似于1),但是在规范中使用了持久性模型。这允许将规范直接用作我们的的一部分。其中谓词将被转换为查询(即TSQL),并且过滤将在持久性存储上执行(即SQL Server)。

This is similar to 1), but using Persistence Models in the specification. This allows direct use of the Specification as part of our .Where predicate which will be translated into a query (i.e. TSQL) and the filtering will be performed on the Persistence storage (i.e. SQL Server).


  1. 虽然这给我们带来了良好的性能,但显然违反了DDD模式。我们的持久性模型泄漏到域层中,使域层依赖于持久性层,而不是其他方式。

3)像2),但将规范作为持久层的一部分


  1. 这不起作用,因为域层需要引用规范。它仍然取决于持久层。

  2. 我们将在持久层内部拥有业务逻辑。这也违反了DDD模式

4)像3一样,但是使用规范作为接口抽象

我们将在域层中有规范接口,在持久层中有规范的具体实现。现在,我们的域层将仅与接口交互,而不依赖于持久层。

We would have Specification interfaces in our Domain layer, our concrete implementations of the Specifications in the Persistence Layer. Now our Domain Layer would only interact with the interfaces and not depend on the Persistence layer.


  1. 这仍然违反了3中的#2)。我们将在持久层中使用业务逻辑,这很糟糕。

5)将表达式树从域模型转换为持久性模型

这当然可以解决问题,但这不是一件容易的事,但是它将规范保留在我们的域层中,同时仍然受益于SQL优化,因为该规范成为Repositories Where子句的一部分并转换为TSQL

This certainly solves the problem, but it's non-trivial task but it would keep the Specifications inside our Domain Layer while still benefiting from SQL optimization, because the Specifications becomes part of the Repositories Where clause and translates into TSQL

我尝试过这种方法,并且存在几个问题(表单实现方面):

I tried going this approach and there are several issues (form implementation side):


  1. 我们需要从Mapper中了解配置(如果使用的话)或保留我们自己的映射系统。这可以使用AutoMapper部分完成(读取Mapper配置),但是还存在其他问题

  2. 对于其中模​​型A的一个属性映射到模型B的一个属性的情况,这是可以接受的。如果类型不同(例如,由于持久性类型,例如将枚举另存为字符串或键/值对在另一个表中,并且我们需要在解析器内部进行转换),则很难。

  3. 它如果将多个字段映射到一个目标字段中,将变得非常复杂,我认为对于域模型->持久性模型映射

6)像API这样的查询生成器

最后一个是制作某种查询API,并将其传递到规范中,并由谁来存储库/持久层将生成一个表达式树,该表达式树将传递到 .Where 子句,该子句使用接口声明所有可过滤字段。

The last one is making some kind of query API which is passed into the specification and from whom the Repository/Persistence layer would generate an Expression Tree to be passed to .Where clause and which uses an Interface to declare all filterable fields.

我也朝这个方向做过几次尝试,但是对结果并不满意。

I did a few attempts in that direction too, but wasn't too happy about the results. Something like

public interface IQuery<T>
{
    IQuery<T> Where(Expression<Func<T, T>> predicate);
}
public interface IQueryFilter<TFilter>
{
    TFilter And(TFilter other);
    TFilter Or(TFilter other);
    TFilter Not(TFilter other);
}

public interface IQueryField<TSource, IQueryFilter>
{
    IQueryFilter Equal(TSource other);
    IQueryFilter GreaterThan(TSource other);
    IQueryFilter Greater(TSource other);
    IQueryFilter LesserThan(TSource other);
    IQueryFilter Lesser(TSource other);
}
public interface IPersonQueryFilter : IQueryFilter<IPersonQueryFilter>
{
    IQueryField<int, IPersonQueryFilter> ID { get; }
    IQueryField<string, IPersonQueryFilter> Name { get; }
    IQueryField<int, IPersonQueryFilter> Age { get; }
}

在规范中,我们将传递 IQuery< ; IPersonQueryFilter>查询到规范构造函数,然后在使用或组合时对其应用规范。

and in the specification we would pass a IQuery<IPersonQueryFilter> query to the specifications constructor and then apply the specifications to it when using or combining it.

IQuery<IGridQueryFilter> query = null;

query.Where(f => f.Name.Equal("Bob") );

我不太喜欢这种方法,因为它使处理复杂的规范变得有些困难(例如and和or (如果已链接),我不喜欢And / Or / Not的工作方式,尤其是通过此 API创建表达式树。

I don't like this approach much, as it makes handling complex specifications somewhat hard (like and or if chaining) and I don't like the way the And/Or/Not would work, especially creating expression trees from this "API".

我一直在互联网上寻找,阅读了数十篇有关DDD和Specification的文章,但它们始终只处理简单的案例,不要考虑性能,否则会违反DDD模式。

I have been looking for weeks all over the Internet, read dozens of articles on DDD and Specification, but they always only handle simple cases and don't take the performance into consideration or they violate DDD pattern.

如何在现实应用中解决此问题而不进行内存过滤或将持久性泄漏到域层中?

How do you solve this in a real world application without doing in memory filtering or leaking Persistence into Domain Layer??

是否有任何一种框架可以通过以下两种方法之一来解决上述问题(查询生成器,如表达式树的语法或表达式树转换器)?

Are there any frameworks which solve issues above with one of the two ways (Query Builder like syntax to Expression Trees or an Expression Tree translator)?

推荐答案

我认为规范模式不适用于查询条件。实际上,DDD的整个概念也不是。如果查询需求过多,请考虑使用CQRS。

I think Specification pattern is not designed for query criteria. Actually, the whole concept of DDD is not, either. Consider CQRS if there are plethora of query requirements.

规范模式可以帮助开发无处不在的语言,我认为这就像是DSL。它声明要做什么而不是怎么做。例如,在订购环境中,如果已下订单但未在30分钟内付款,则视为逾期。使用规范模式,您的团队可以说一个简短而独特的术语:OverdueOrderSpecification。想象下面的讨论:

Specification pattern helps develop ubiquitous language, I think it's like kind of a DSL. It declares what to do rather than how to do it. For example, in a ordering context, orders are considered as overdue if it was placed but not paid within 30 minutes. With Specification pattern, your team can talk with a short but unique term: OverdueOrderSpecification. Imagine the discussion below:

情况-1

Business people: I want to find out all overdue orders and ...  
Developer: I can do that, it is easy to find all satisfying orders with an overdue order specification and..

case -2

Business people: I want to find out all orders which were placed before 30 minutes and still unpaid...  
Developer: I can do that, it is easy to filter order from tbl_order where placed_at is less that 30minutes before sysdate....

您更喜欢哪个?

通常,我们需要一个DSL处理程序来解析dsl,在这种情况下,它可能在持久性适配器中,将规范转换为查询条件。这种依赖关系(infrastrructure.persistence =>域)不违反体系结构主体。

Usually, we need a DSL handler to parse the dsl, in this case, it may be in the persistence adapter, translates the specification to a query criteria. This dependence (infrastrructure.persistence => domain) does not violates the architecture principal.

class OrderMonitorApplication {
    public void alarm() {
       // The specification pattern keeps the overdue order ubiquitous language in domain
       List<Order> overdueOrders = orderRepository.findBy(new OverdueSpecification());
       for (Order order: overdueOrders) {
           //notify admin
       }
    }
}

class HibernateOrderRepository implements orderRepository {
    public List<Order> findBy(OrderSpecification spec) {
        criteria.le("whenPlaced", spec.placedBefore())//returns sysdate - 30
        criteria.eq("status", spec.status());//returns WAIT_PAYMENT
        return ...
    }
}

这篇关于域驱动设计中的规范模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆