领域驱动设计中的规范模式

Tse*_*eng 21 domain-driven-design specifications repository repository-pattern

我一直在努力解决与规范有关的DDD相关问题,我已经阅读了很多关于DDD和规范和存储库的内容.

但是,如果尝试在不破坏域驱动设计的情况下组合所有这三个,则会出现问题.它归结为如何应用具有性能的过滤器.

首先是一些明显的事实:

  1. 获取DataAccess/Infrastructure Layer的存储库
  2. 域模型表示业务逻辑并转到域层
  3. 数据访问模型表示持久层,并转到Persistance/Infrastructure/DataAccess层
  4. 业务逻辑转到域层
  5. 规格是业务逻辑,因此它们也属于域层.
  6. 在所有这些示例中,在存储库中使用ORM框架和SQL Server
  7. 持久性模型可能不会泄漏到域层

到目前为止,这么容易.当/如果我们尝试将规范应用于存储库而不破坏DDD模式或存在性能问题时,就会出现问题.

申请规格的可能方式:

1)经典方式:使用域层中的域模型的规范

应用传统的规格模式,使用IsSatisfiedBy方法,返回bool和复合规格以组合多个规格.

这让我们保持域层的规范,但......

  1. 它必须与域模型一起使用,而存储库使用持久性模型来表示持久层的数据结构.这个很容易修复使用映射器,如AutoMapper.
  2. 但是,问题无法解决:所有规格都必须在内存中执行.在大型表/数据库中,如果您必须遍历所有实体仅过滤掉符合您规范的实体,这意味着巨大的影响

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

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

  1. 虽然这会提供良好的性能,但它显然违反了DDD模式.我们的持久性模型泄漏到域层,使得域层依赖于持久层,而不是相反.

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

  1. 这不起作用,因为域层需要引用规范.它仍然依赖于持久层.
  2. 我们在持久层内部会有业务逻辑.这也违反了DDD模式

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

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

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

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

这当然解决了这个问题,但这是一项非常重要的任务,但它会将规范保留在我们的域层内,同时仍然受益于SQL优化,因为规范成为Repositories Where子句的一部分并转换为TSQL

我试过这种方法,有几个问题(表单实现方面):

  1. 我们需要从Mapper知道配置(如果我们使用它)或保留我们自己的映射系统.这可以部分完成(使用AutoMapper读取Mapper配置),但存在进一步的问题
  2. 对于模型A的一个属性映射到模型B的一个属性的情况是可接受的.如果类型不同(即由于持久性类型,例如枚举被保存为另一个表中的字符串或键/值对,并且我们需要在解析器内进行转换.
  3. 如果多个字段映射到一个目标字段,它会变得非常复杂.我认为这不是域模型 - >持久性模型映射的问题

**6)查询生成器,如API**

最后一个是制作某种查询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; }
}
Run Code Online (Sandbox Code Playgroud)

在规范中,我们将a传递IQuery<IPersonQueryFilter> query给规范构造函数,然后在使用或组合它时将规范应用于它.

IQuery<IGridQueryFilter> query = null;

query.Where(f => f.Name.Equal("Bob") );
Run Code Online (Sandbox Code Playgroud)

我不太喜欢这种方法,因为它使处理复杂的规范有点困难(喜欢和/或链接)并且我不喜欢And/Or/Not的工作方式,特别是从这个"API"创建表达式树.

我一直在互联网上寻找几周,阅读了几十篇关于DDD和规范的文章,但它们总是只处理简单的情况,不考虑性能或违反DDD模式.

如何在现实世界的应用程序中解决这个问题而不进行内存过滤或将持久性泄漏到域层?

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

Yug*_*hou 7

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

规范模式有助于开发无处不在的语言,我认为这就像一种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..
Run Code Online (Sandbox Code Playgroud)

情况-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....
Run Code Online (Sandbox Code Playgroud)

你更倾向哪个?

通常,我们需要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 ...
    }
}
Run Code Online (Sandbox Code Playgroud)


Fab*_*eco 6

我\xc2\xb4m 参加聚会迟到了,bug 这是我的 2 美分...

\n\n

由于与您上面描述的完全相同的原因,我也确实在实现规范模式方面遇到了困难。如果您放弃对单独模型(持久性/域)的要求,那么您的问题就会大大简化。您可以在规范中添加另一种方法来生成 ORM 的表达式树:

\n\n
public interface ISpecification<T>\n{\n    bool IsSpecifiedBy(T item);\n    Expression<Func<T, bool>> GetPredicate()\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

Valdmir Khorikov发表了一篇文章,详细描述了如何做到这一点。

\n\n

然而,我真的不喜欢有一个单一的模型。和您一样,我发现持久化模型应该保留在基础设施层中,以免由于 ORM 限制而污染您的域。

\n\n

最终我想出了一个解决方案,使用访问者将域模型转换为持久性模型的表达式树。

\n\n

我最近写了一系列文章,其中我解释了

\n\n\n\n

最终结果实际上使用起来非常简单,您\xc2\xb4ll需要使规范可访问...

\n\n
public interface IProductSpecification\n{\n    bool IsSpecifiedBy(Product item);\n    TResult Accept<TResult>(IProductSpecificationVisitor<TResult> visitor);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

创建一个SpecificationVisitor将规范转换为表达式:

\n\n
public class ProductEFExpressionVisitor : IProductSpecificationVisitor<Expression<Func<EFProduct, bool>>> \n{\n    public Expression<Func<EFProduct, bool>> Visit (ProductMatchesCategory spec) \n    {\n        var categoryName = spec.Category.CategoryName;\n        return ef => ef.Category == categoryName;\n    }\n\n    //other specification-specific visit methods\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您想创建通用规范,则需要进行一些调整。它\xc2\xb4s在上面引用的帖子中都有详细说明。

\n


Val*_* P. 5

一旦我实现了规范,但是...

  1. 它基于 LINQ 和 IQueryable。
  2. 它使用了单一的统一存储库(但对我来说还不错,我认为这是使用规范的主要原因)。
  3. 它使用单一模型来满足域和持久性需求(我认为这很糟糕)。

存储库:

public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot
{
    TEntity Get<TKey>(TKey id);

    TEntity TryGet<TKey>(TKey id);

    void DeleteByKey<TKey>(TKey id);

    void Delete(TEntity entity);

    void Delete(IEnumerable<TEntity> entities);

    IEnumerable<TEntity> List(FilterSpecification<TEntity> specification);

    TEntity Single(FilterSpecification<TEntity> specification);        

    TEntity First(FilterSpecification<TEntity> specification);

    TResult Compute<TResult>(ComputationSpecification<TEntity, TResult> specification);

    IEnumerable<TEntity> ListAll();

    //and some other methods
}
Run Code Online (Sandbox Code Playgroud)

过滤器规格:

public abstract class FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot
{

     public abstract IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots);

     public static FilterSpecification<TAggregateRoot> CreateByPredicate(Expression<Func<TAggregateRoot, bool>> predicate)
     {
         return new PredicateFilterSpecification<TAggregateRoot>(predicate);
     }      

     public static FilterSpecification<TAggregateRoot> operator &(FilterSpecification<TAggregateRoot> op1, FilterSpecification<TAggregateRoot> op2)
     {
         return new CompositeFilterSpecification<TAggregateRoot>(op1, op2);
     }        

     public static FilterSpecification<TAggregateRoot> CreateDummy()
     {
         return new DummyFilterSpecification<TAggregateRoot>();
     }

}


public class CompositeFilterSpecification<TAggregateRoot> : FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot
{

    private readonly FilterSpecification<TAggregateRoot> _firstOperand;
    private readonly FilterSpecification<TAggregateRoot> _secondOperand;

    public CompositeFilterSpecification(FilterSpecification<TAggregateRoot> firstOperand, FilterSpecification<TAggregateRoot> secondOperand)
    {
        _firstOperand = firstOperand;
        _secondOperand = secondOperand;
    }

    public override IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots)
    {
        var operand1Results = _firstOperand.Filter(aggregateRoots);
        return _secondOperand.Filter(operand1Results);
    }
}

public class PredicateFilterSpecification<TAggregateRoot> : FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot
{

    private readonly Expression<Func<TAggregateRoot, bool>> _predicate;

    public PredicateFilterSpecification(Expression<Func<TAggregateRoot, bool>> predicate)
    {
        _predicate = predicate;
    }

    public override IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots)
    {
        return aggregateRoots.Where(_predicate);
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种规格:

public abstract class ComputationSpecification<TAggregateRoot, TResult> where TAggregateRoot : Entity, IAggregateRoot
{

    public abstract TResult Compute(IQueryable<TAggregateRoot> aggregateRoots);

    public static CompositeComputationSpecification<TAggregateRoot, TResult> operator &(FilterSpecification<TAggregateRoot> op1, ComputationSpecification<TAggregateRoot, TResult> op2)
    {
        return new CompositeComputationSpecification<TAggregateRoot, TResult>(op1, op2);
    }

}
Run Code Online (Sandbox Code Playgroud)

和用法:

OrderRepository.Compute(new MaxInvoiceNumberComputationSpecification()) + 1
PlaceRepository.Single(FilterSpecification<Place>.CreateByPredicate(p => p.Name == placeName));
UnitRepository.Compute(new UnitsAreAvailableForPickingFilterSpecification() & new CheckStockContainsEnoughUnitsOfGivenProductComputatonSpecification(count, product));
Run Code Online (Sandbox Code Playgroud)

自定义实现可能看起来像

public class CheckUnitsOfGivenProductExistOnPlaceComputationSpecification : ComputationSpecification<Unit, bool>
{
    private readonly Product _product;
    private readonly Place _place;

    public CheckUnitsOfGivenProductExistOnPlaceComputationSpecification(
        Place place,
        Product product)
    {
        _place = place;
        _product = product;
    }

    public override bool Compute(IQueryable<Unit> aggregateRoots)
    {
        return aggregateRoots.Any(unit => unit.Product == _product && unit.Place == _place);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我不得不说,Specficiation根据 DDD ,简单的实现并不适合。你在这个领域做了大量的研究,不太可能有人提出新的东西:)。另外,请查看http://www.sapiensworks.com/blog/博客。