|
在上一篇文章《.NET應(yīng)用框架架構(gòu)設(shè)計(jì)實(shí)踐 - 概述》的評(píng)論部分,有網(wǎng)友提出了一個(gè)在面向領(lǐng)域驅(qū)動(dòng)架構(gòu)的實(shí)踐中比較常見的問題:“DDD使用聚合根訪問,那例如那些通用查詢?nèi)绾螌?shí)現(xiàn)?難道都要經(jīng)過聚合根多步得到么?DDD如何實(shí)現(xiàn)關(guān)聯(lián)表的查詢,例如3表關(guān)聯(lián)查詢?”這個(gè)問題比較泛,涉及的內(nèi)容也比較多,我就單獨(dú)一篇文章介紹一下我對(duì)這個(gè)問題的看法。關(guān)于上面問題中的“通用查詢”- 呃,這個(gè)定義比較模糊,我只能給出我的一些想法或者經(jīng)驗(yàn)性的東西,我在本文中的經(jīng)驗(yàn)與觀點(diǎn)并不一定會(huì)100%適合您的應(yīng)用場(chǎng)景,但我想應(yīng)該還是具有一定指導(dǎo)性意義的。
聚合與聚合根
我想,還是從聚合根談起吧。聚合根是DDD中的概念,不管是經(jīng)典的DDD架構(gòu),還是基于事件驅(qū)動(dòng)的CQRS架構(gòu),其實(shí)它們之間絕大部分概念都是相通的,比如實(shí)體、值對(duì)象、服務(wù)、工廠、倉儲(chǔ)以及聚合/聚合根等。根據(jù)我的理解,聚合根是一個(gè)實(shí)體,它保持著與其它實(shí)體/值對(duì)象的引用,并與這些實(shí)體/值對(duì)象一起,來表達(dá)領(lǐng)域的通用語言中的一個(gè)唯一的無二義的邏輯概念。比如最常見的“客戶(Customer)”,在“在線銷售”的領(lǐng)域中,“客戶”不僅包含它所指代的那個(gè)個(gè)人(或者是組織)的名稱、聯(lián)系電話、聯(lián)系電郵,還會(huì)包含它的聯(lián)系地址(Contact Address)以及送貨地址(Delivery Address),那么就Address而言,在此我們可以將其視為值對(duì)象,因?yàn)槲覀冎魂P(guān)心地址本身所包含的信息。在這里,“客戶(Customer)”不僅是實(shí)體,而且是“客戶-地址”所組成的對(duì)象集合(聚合)的聚合根。
在這里會(huì)有異議的地方就是“銷售訂單(Sales Order)”是否應(yīng)該屬于“客戶(Customer)”聚合。我覺得這還是要看在當(dāng)前的領(lǐng)域中,“銷售訂單”是不是“客戶”的必有信息,換句話說,“客戶”是不是沒有“銷售訂單”就不成其為“客戶”。我想,在大多數(shù)情況下,“客戶”應(yīng)該是一個(gè)可以脫離“銷售訂單”而單獨(dú)存在的實(shí)體,那這樣的話,“銷售訂單”也將不屬于“客戶”聚合。
現(xiàn)在讓我們來看“在線銷售”領(lǐng)域中的另一部分:銷售訂單。當(dāng)然,“銷售訂單(Sales Order)”是實(shí)體,本身也是訂單主體與“訂單明細(xì)(Sales Lines)”所組成的聚合的聚合根,這是很自然的事情,因?yàn)?ldquo;銷售訂單”如果沒有訂單的明細(xì)信息,也就失去了訂單本身的意義。此外,“客戶”實(shí)體也是這個(gè)聚合的一個(gè)組成部分,這也很好理解,“銷售訂單”本身就是客戶下達(dá)的,它不可能脫離“客戶”而憑空存在。于是,以“銷售訂單”為根的聚合,還包括“客戶”實(shí)體,以及“訂單明細(xì)”(至于“訂單明細(xì)”是實(shí)體還是值對(duì)象,這跟具體的領(lǐng)域定義有密切關(guān)系,比如如果涉及商品Item與購買量的打折等內(nèi)容,那么“訂單明細(xì)”就需要以實(shí)體方式處理,否則可以設(shè)計(jì)成“值對(duì)象”以減小系統(tǒng)開銷,本文繞過這個(gè)問題的討論)。在作進(jìn)一步討論之前,讓我們回顧一下DDD中的倉儲(chǔ)。DDD告訴我們,倉儲(chǔ)是作用在聚合根上的:領(lǐng)域模型中對(duì)象的保存與讀取都是以聚合為單位而進(jìn)行的。
通過上面的討論,針對(duì)“在線銷售”領(lǐng)域,我們大致得到了如下的領(lǐng)域模型(為了縮短篇幅,圖中可能會(huì)省略某些部分)
問題來了,如果我們需要獲得某個(gè)“客戶”的所有訂單,該怎么辦?在上面的領(lǐng)域模型中,Customer實(shí)體并沒有某個(gè)屬性或者方法來獲得其所有的銷售訂單。那么在遇到這樣的問題時(shí),通常都是通過SalesOrder的倉儲(chǔ),配合規(guī)約(Specification)來篩選出所有符合特定“客戶”條件的銷售訂單,然后由倉儲(chǔ)返回銷售訂單的列表。你或許會(huì)覺得這種做法比較不科學(xué),你會(huì)覺得應(yīng)該通過Customer實(shí)體的某個(gè)屬性(比如SalesOrders)來獲得該“客戶”所擁有的所有銷售訂單,這樣會(huì)更直截了當(dāng)些。但在上面我們已經(jīng)對(duì)這個(gè)領(lǐng)域模型進(jìn)行了討論,在我們的案例中,Customer是一個(gè)獨(dú)立的實(shí)體,SalesOrder不是它的必要組成部分。于是,為了維護(hù)領(lǐng)域模型的完整性,我們需要利用“銷售訂單”的倉儲(chǔ)來完成這個(gè)功能。偽代碼如下:
{
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知識(shí)庫:面向領(lǐng)域驅(qū)動(dòng)架構(gòu)的查詢實(shí)現(xiàn)方式,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。