面向领域驱动架构的查询实现方式

  在上一篇文章《.NET应用框架架构设计实践 - 概述》的评论部分,有网友提出了一个在面向领域驱动架构的实践中比较常见的问题:“DDD使用聚合根访问,那例如那些通用查询如何实现?难道都要经过聚合根多步得到么?DDD如何实现关联表的查询,例如3表关联查询?”这个问题比较泛,涉及的内容也比较多,我就单独一篇文章介绍一下我对这个问题的看法。关于上面问题中的“通用查询”- 呃,这个定义比较模糊,我只能给出我的一些想法或者经验性的东西,我在本文中的经验与观点并不一定会100%适合您的应用场景,但我想应该还是具有一定指导性意义的。

  聚合与聚合根

  我想,还是从聚合根谈起吧。聚合根是DDD中的概念,不管是经典的DDD架构,还是基于事件驱动的CQRS架构,其实它们之间绝大部分概念都是相通的,比如实体、值对象、服务、工厂、仓储以及聚合/聚合根等。根据我的理解,聚合根是一个实体,它保持着与其它实体/值对象的引用,并与这些实体/值对象一起,来表达领域的通用语言中的一个唯一的无二义的逻辑概念。比如最常见的“客户(Customer)”,在“在线销售”的领域中,“客户”不仅包含它所指代的那个个人(或者是组织)的名称、联系电话、联系电邮,还会包含它的联系地址(Contact Address)以及送货地址(Delivery Address),那么就Address而言,在此我们可以将其视为值对象,因为我们只关心地址本身所包含的信息。在这里,“客户(Customer)”不仅是实体,而且是“客户-地址”所组成的对象集合(聚合)的聚合根。

  在这里会有异议的地方就是“销售订单(Sales Order)”是否应该属于“客户(Customer)”聚合。我觉得这还是要看在当前的领域中,“销售订单”是不是“客户”的必有信息,换句话说,“客户”是不是没有“销售订单”就不成其为“客户”。我想,在大多数情况下,“客户”应该是一个可以脱离“销售订单”而单独存在的实体,那这样的话,“销售订单”也将不属于“客户”聚合。

  现在让我们来看“在线销售”领域中的另一部分:销售订单。当然,“销售订单(Sales Order)”是实体,本身也是订单主体与“订单明细(Sales Lines)”所组成的聚合的聚合根,这是很自然的事情,因为“销售订单”如果没有订单的明细信息,也就失去了订单本身的意义。此外,“客户”实体也是这个聚合的一个组成部分,这也很好理解,“销售订单”本身就是客户下达的,它不可能脱离“客户”而凭空存在。于是,以“销售订单”为根的聚合,还包括“客户”实体,以及“订单明细”(至于“订单明细”是实体还是值对象,这跟具体的领域定义有密切关系,比如如果涉及商品Item与购买量的打折等内容,那么“订单明细”就需要以实体方式处理,否则可以设计成“值对象”以减小系统开销,本文绕过这个问题的讨论)。在作进一步讨论之前,让我们回顾一下DDD中的仓储。DDD告诉我们,仓储是作用在聚合根上的:领域模型中对象的保存与读取都是以聚合为单位而进行的。

  通过上面的讨论,针对“在线销售”领域,我们大致得到了如下的领域模型(为了缩短篇幅,图中可能会省略某些部分)

image  问题来了,如果我们需要获得某个“客户”的所有订单,该怎么办?在上面的领域模型中,Customer实体并没有某个属性或者方法来获得其所有的销售订单。那么在遇到这样的问题时,通常都是通过SalesOrder的仓储,配合规约(Specification)来筛选出所有符合特定“客户”条件的销售订单,然后由仓储返回销售订单的列表。你或许会觉得这种做法比较不科学,你会觉得应该通过Customer实体的某个属性(比如SalesOrders)来获得该“客户”所拥有的所有销售订单,这样会更直截了当些。但在上面我们已经对这个领域模型进行了讨论,在我们的案例中,Customer是一个独立的实体,SalesOrder不是它的必要组成部分。于是,为了维护领域模型的完整性,我们需要利用“销售订单”的仓储来完成这个功能。伪代码如下:

public interface ISpecification<T>
{
bool IsSatisfiedBy(T obj);
}

public abstract class Specification<T> : ISpecification<T>
{
public abstract Expression<Func<T, bool>> Expression { get; }

public bool IsSatisfiedBy(T obj)
{
return this.Expression.Compile()(obj);
}
}

public class OrderCustomerMatchesSpecification : Specification<SalesOrder>
{
private Customer customer;
public OrderCustomerMatchesSpecification(Customer customer)
{
this.customer = customer;
}
public override Expression<Func<SalesOrder, bool>> Expression
{
get { return p => p.Customer.Id.Equals(customer.Id); }
}
}

public interface IRepository<T>
where T : IAggregateRoot
{
void Add(T aggregateRoot);
List
<T> GetAllBySpecification(ISpecification<T> spec);
}

public class MemoryRepository<T> : IRepository<T>
where T : IAggregateRoot
{
private readonly List<T> store =new List<T>();

public void Add(T aggregateRoot)
{
if (!this.store.Exists(p => p.Id.Equals(aggregateRoot.Id)))
this.store.Add(aggregateRoot);
}

public List<T> GetAllBySpecification(ISpecification<T> spec)
{
return this.store.Where(spec.IsSatisfiedBy).ToList();
}
}

ISpecification
<SalesOrder> spec =new OrderCustomerMatchesSpecification(custDaxNET);
List
<SalesOrder> daxNETOrders = salesOrderRepository.GetAllBySpecification(spec);

it知识库面向领域驱动架构的查询实现方式,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。