但是,如果尝试在不破坏域驱动设计的情况下组合所有这三个,则会出现问题.它归结为如何应用具有性能的过滤器.
首先是一些明显的事实:
>获取DataAccess / Infrastructure Layer的存储库
>域模型表示业务逻辑并转到域层
>数据访问模型表示持久层,并转到Persistance / Infrastructure / DataAccess层
>业务逻辑进入域层
>规格是业务逻辑,因此它们也属于域层.
>在所有这些示例中,在存储库中使用ORM框架和sql Server
>持久模型可能不会泄漏到域层
到目前为止,这么容易.当/如果我们尝试将规范应用于存储库而不破坏DDD模式或出现性能问题时,就会出现问题.
申请规格的可能方式:
1)经典方式:使用域层中的域模型的规范
应用传统的规范模式,使用IsSatisfiedBy方法,返回bool和复合规范以组合多个规范.
这让我们保持域层中的规范,但……
>它必须与域模型一起使用,而存储库使用持久性模型来表示持久层的数据结构.使用像AutoMapper这样的映射器很容易解决这个问题.
>然而,问题无法解决:所有规格都必须在内存中执行.在大型表/数据库中,如果您必须遍历所有实体以过滤掉符合您规范的实体,这意味着巨大的影响
2)使用持久性模型的规范
这类似于1),但在规范中使用持久性模型.这允许直接使用规范作为我们的.Where谓词的一部分,该谓词将被转换为查询(即Tsql),并且将在持久性存储(即sql Server)上执行过滤.
>虽然这会提供良好的性能,但它显然违反了DDD模式.我们的持久性模型泄漏到域层,使得域层依赖于持久层,而不是相反.
3)像2),但使规范成为持久层的一部分
>这不起作用,因为Domain Layer需要引用规范.它仍然依赖于持久层.
>我们在持久层内部会有业务逻辑.这也违反了DDD模式
4)像3一样,但使用抽象规范作为接口
我们在Domain层中有规范接口,我们在持久层中具体实现了规范.现在我们的域层只与接口交互而不依赖于持久层.
>这仍然违反了3中的#2.我们在持久层中会有业务逻辑,这很糟糕.
5)将表达式树从域模型转换为持久性模型
这当然解决了这个问题,但这是非常重要的任务,但是它会将规范保留在我们的域层中,同时仍然受益于sql优化,因为规范成为Repositories Where子句的一部分并转换为Tsql
我试过这种方法,有几个问题(表单实现方面):
>我们需要从Mapper知道配置(如果我们使用它)或保留我们自己的映射系统.这可以部分完成(使用AutoMapper读取Mapper配置),但存在进一步的问题
>对于模型A的一个属性映射到模型B的一个属性的情况是可接受的.如果类型不同(即由于持久性类型,例如枚举被保存为另一个表中的字符串或键/值对,则变得更加困难)我们需要在解析器内进行转换.
>如果多个字段映射到一个目标字段,则会变得非常复杂.我认为这不是域模型的问题 – >持久性模型映射
最后一个是制作某种查询API,该API被传递到规范中,Repository / Persistence层将从中生成表达式树,以传递给.Where子句,并使用接口声明所有可过滤字段.
我也在那个方向做过几次尝试,但对结果并不太满意.就像是
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>查询规范构造函数,然后在使用或组合时将规范应用于它.
IQuery<IGridQueryFilter> query = null; query.Where(f => f.Name.Equal("Bob") );
我不太喜欢这种方法,因为它使处理复杂的规范有点困难(喜欢和/或链接),我不喜欢And / Or / Not的工作方式,特别是从这个“API”创建表达式树.
我一直在互联网上寻找几个星期,阅读了几十篇关于DDD和规范的文章,但它们总是只处理简单的情况,不考虑性能或违反DDD模式.
如何在现实世界的应用程序中解决这个问题而不进行内存过滤或将持久性泄漏到域层?
解决方法
规范模式有助于开发无处不在的语言,我认为它就像一种DSL.它声明要做什么而不是如何做.例如,在订购上下文中,如果订单已在30分钟内下达但未付款,则认为订单已过期.使用规范模式,您的团队可以使用简短但独特的术语:OverdueOrderSpecification.想象一下下面的讨论:
情况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..
案例-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 =>域)不违反体系结构原则.
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 ... } }