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

时间:2014-09-20 22:17:29

标签: repository domain-driven-design repository-pattern specifications

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

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

首先是一些明显的事实:

  1. 获取DataAccess / Infrastructure Layer的存储库
  2. 域模型代表业务逻辑并转到域层
  3. 数据访问模型表示持久层,并转到Persistance / Infrastructure / DataAccess图层
  4. 业务逻辑转到域层
  5. 规格是商业逻辑,因此它们也属于域层。
  6. 在所有这些示例中,在存储库
  7. 中使用了ORM框架和SQL Server
  8. 持续模型可能不会泄漏到域层
  9. 到目前为止,这么容易。当/如果我们尝试将规范应用于存储库而不破坏DDD模式或存在性能问题时,就会出现问题。

    应用规格的可能方式:

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

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

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

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

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

      1. 虽然这会提供良好的性能,但它显然违反了DDD模式。我们的持久性模型泄漏到域层,使得域层依赖于持久层,而不是相反。
      2. 3)与2)类似,但将规范作为持久层的一部分

        1. 这不起作用,因为域层需要引用规范。它仍然依赖于持久层。
        2. 我们在持久层内部会有业务逻辑。这也违反了DDD模式
        3. 4)与3类似,但使用抽象规范作为接口

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

          1. 这仍然违反了3中的#2。我们在持久层中会有业务逻辑,这很糟糕。
          2. 5)将表达式树从域模型转换为持久性模型

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

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

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

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

              IQuery<IGridQueryFilter> query = null;
              
              query.Where(f => f.Name.Equal("Bob") );
              

              我不太喜欢这种方法,因为它使得处理复杂的规范有点困难(比如和链接),我不喜欢And / Or / Not的工作方式,特别是创建来自这个&#34; API&#34;的表达树。

              我一直在互联网上寻找,阅读了几十篇关于DDD和规格的文章,但他们总是只处理简单的案例并且不考虑性能或他们违反DDD模式。

              如何在没有内存过滤或将持久性泄漏到域层中的情况下在现实世界的应用程序中解决这个问题?

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

4 个答案:

答案 0 :(得分:6)

我认为规范模式不是针对查询条件设计的。实际上,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..

案例-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 =&gt;域)不违反架构原则。

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 ...
    }
}

答案 1 :(得分:4)

我实施规范但......

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

    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
    }
    

    过滤器规格:

    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);
        }
    }
    

    另一种规格:

    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);
        }
    
    }
    

    和用法:

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

    自定义实现可能看起来像

    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);
        }
    }
    

    最后,根据DDD,我被迫告诉简单的Specficiation实施不合适。你已经在这方面做了很多研究,并且不太可能有人提出新的东西:)。另外,请查看http://www.sapiensworks.com/blog/博客。

答案 2 :(得分:1)

  

我一直在互联网上寻找几个星期,阅读了几十个   关于DDD和规范的文章,但它们总是只处理简单   案件,不考虑性能或他们   违反DDD模式。

如果我错了,有人会纠正我,但在我看来,“持久模型”的概念直到最近才出现在DDD空间中(顺便说一句,你在哪里读过它? )。我不确定它是否在原版蓝皮书中有描述。

我个人并没有看到很多优点。我的观点是,您的数据库中存在持久(通常)关系模型,并且您的应用程序中存在内存域模型。两者之间的差距由一个行动来弥补,而不是一个模型。此操作可以由ORM执行。我还没有出售“持久对象模型”在语义上真正有意义的事实,更不用说尊重DDD原则(*)。

现在有CQRS方法,你有一个单独的阅读模型,但这是一个完全不同的动物,在这种情况下我不会看到Specifications作用于阅读模型对象而不是实体作为DDD违规。规范毕竟是一个非常笼统的模式,DDD中没有任何内容从根本上限制实体。

(*)编辑:Automapper创建者Jimmy Bogard似乎也发现它过于复杂 - 请参阅How do I use automapper to map many-to-many relationships?

答案 3 :(得分:1)

我参加聚会迟到了,这里的错误是我的2美分......

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

public interface ISpecification<T>
{
    bool IsSpecifiedBy(T item);
    Expression<Func<T, bool>> GetPredicate()
}

post from Valdmir Khorikov描述如何详细说明。

然而,我真的不喜欢单一型号。和我一样,我发现Peristence模型应保留在基础架构层中,以免因ORM限制而污染您的域。

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

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

实际使用的最终结果非常简单,您需要制作规范 Visitable ...

public interface IProductSpecification
{
    bool IsSpecifiedBy(Product item);
    TResult Accept(IProductSpecificationVisitor<TResult> visitor);
}

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

public class ProductEFExpressionVisitor : IProductSpecificationVisitor<Expression<Func<EFProduct, bool>>> 
{
    public Expression<Func<EFProduct, bool>>Visit (ProductMatchesCategory spec) 
    {
        var categoryName = spec.Category.CategoryName;
        return ef => ef.Category == categoryName;
    }

    //other specification-specific visit methods
}

如果你想创建一个通用的spefication,只需要做一些tweeking。这些都在上面提到的帖子中详述。