我一直在努力解决与规范有关的DDD相关问题,并且我已经阅读了很多DDD,规范和存储库。
但是,如果尝试在不破坏域驱动设计的情况下组合所有这三个,则会出现问题。它归结为如何应用具有性能的过滤器。
首先是一些明显的事实:
到目前为止,这么容易。当/如果我们尝试将规范应用于存储库而不破坏DDD模式或存在性能问题时,就会出现问题。
应用规格的可能方式:
1)经典方式:使用域层中的域模型的规范
使用IsSatisfiedBy
方法应用传统的规范模式,返回bool
和复合规范以组合多个规范。
这让我们在域层中保留规范,但是......
AutoMapper
)很容易解决这个问题。 2)使用持久性模型的规范
这类似于1),但在规范中使用了持久性模型。这允许直接使用规范作为我们的.Where
谓词的一部分,该谓词将被转换为查询(即TSQL),并且将在持久性存储(即SQL Server)上执行过滤。
3)与2)类似,但将规范作为持久层的一部分
4)与3类似,但使用抽象规范作为接口
我们在Domain层中有规范接口,我们在持久层中具体实现了规范。现在我们的域层只与接口交互而不依赖于持久层。
5)将表达式树从域模型转换为持久性模型
这当然解决了这个问题,但这是一项非常重要的任务,但它会将规范保留在我们的域层内,同时仍然受益于SQL优化,因为规范成为了Repositories Where子句的一部分并转换为TSQL
我尝试过这种方法,有几个问题(表单实现方面):
** 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模式。
如何在没有内存过滤或将持久性泄漏到域层中的情况下在现实世界的应用程序中解决这个问题?
是否有任何框架可以通过两种方式之一解决上述问题(查询生成器,如表达式树的语法或表达式树转换器)?
答案 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)
我实施规范但......
存储库:
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。这些都在上面提到的帖子中详述。