|
在上一篇文章《.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告訴我們,倉儲是作用在聚合根上的:領域模型中對象的保存與讀取都是以聚合為單位而進行的。
通過上面的討論,針對“在線銷售”領域,我們大致得到了如下的領域模型(為了縮短篇幅,圖中可能會省略某些部分)
問題來了,如果我們需要獲得某個“客戶”的所有訂單,該怎么辦?在上面的領域模型中,Customer實體并沒有某個屬性或者方法來獲得其所有的銷售訂單。那么在遇到這樣的問題時,通常都是通過SalesOrder的倉儲,配合規約(Specification)來篩選出所有符合特定“客戶”條件的銷售訂單,然后由倉儲返回銷售訂單的列表。你或許會覺得這種做法比較不科學,你會覺得應該通過Customer實體的某個屬性(比如SalesOrders)來獲得該“客戶”所擁有的所有銷售訂單,這樣會更直截了當些。但在上面我們已經對這個領域模型進行了討論,在我們的案例中,Customer是一個獨立的實體,SalesOrder不是它的必要組成部分。于是,為了維護領域模型的完整性,我們需要利用“銷售訂單”的倉儲來完成這個功能。偽代碼如下:
{
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知識庫:面向領域驅動架構的查詢實現方式,轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。