領(lǐng)域模型
實(shí)體與聚合根
讀者和圖書(shū)是實(shí)體拌喉;由于每個(gè)讀者都將有自己的借書(shū)信息(比如来吩,什么時(shí)候借的哪本書(shū),是否已經(jīng)歸還懈涛,或者是否已經(jīng)過(guò)期)逛万,而與之對(duì)應(yīng)地,每本書(shū)也可以有被借歷史(比如,這本書(shū)是什么時(shí)候借給哪個(gè)讀者)宇植,于是得封,借書(shū)信息也是實(shí)體。
再來(lái)看看聚合指郁。借書(shū)信息是與讀者和圖書(shū)關(guān)聯(lián)的忙上,也就是說(shuō),沒(méi)有讀者闲坎,借書(shū)信息沒(méi)有存在的意義疫粥,同樣,沒(méi)有圖書(shū)腰懂,借書(shū)信息也同樣不存在梗逮。每個(gè)讀者可以沒(méi)有任何借書(shū)信息(或者說(shuō)借書(shū)記錄),也可以有多條借書(shū)信息绣溜;而每本書(shū)也同樣可以沒(méi)有任何被借信息(或者說(shuō)被借記錄)慷彤,也可以有多條被借記錄。因此存在兩個(gè)聚合:讀者-借書(shū)信息聚合(1..0.)以及圖書(shū)-借書(shū)信息聚合(1..0.)怖喻。讀者和圖書(shū)分別為聚合根底哗,借書(shū)信息為實(shí)體。與Tiny Library對(duì)應(yīng)起來(lái)锚沸,總結(jié)如下:
讀者:Reader跋选,聚合根
圖書(shū):Book,聚合根
借書(shū)信息:Registration哗蜈,實(shí)體
根據(jù)上述描述野建,我們可以確定,我們將來(lái)需要針對(duì)讀者(Reader)和圖書(shū)(Book)實(shí)現(xiàn)倉(cāng)儲(chǔ)以及相應(yīng)的規(guī)約恬叹。Reader聚合根
public partial class Reader : IAggregateRoot
{
}
- Book聚合根
public partial class Book : IAggregateRoot
{
}
- Registration實(shí)體
public partial class Registration : IEntity
{
}
聚合根、實(shí)體同眯、值對(duì)象的關(guān)系
實(shí)體有ID绽昼,有生命周期,有狀態(tài)(用值對(duì)象描述狀態(tài))须蜗,實(shí)體通過(guò)ID進(jìn)行區(qū)分是這個(gè)實(shí)體還是那個(gè)實(shí)體硅确;
聚合根是實(shí)體,聚合根的ID全局唯一明肮,聚合根下面的實(shí)體的ID在聚合根內(nèi)唯一即可菱农;
值對(duì)象的核心意思是值,與是否是復(fù)雜類(lèi)型無(wú)關(guān)柿估,比如下圖中的Price循未、Count、OrderNo秫舌、CustomerAddress都是值對(duì)象的妖;
值對(duì)象無(wú)生命周期绣檬,它的本質(zhì)是一個(gè)值,通過(guò)兩個(gè)值對(duì)象的值是否相同來(lái)區(qū)分是同一個(gè)值對(duì)象嫂粟;
值對(duì)象用于描述實(shí)體的狀態(tài)娇未;
using System.Collections.Generic;
namespace Rsdf.Net.Boilerplate.Domain.Entities
{
// 聚合根
public class Order
{
public string Id; // 值對(duì)象,訂單的ID,全局唯一
public string OrderNo; // 值對(duì)象
public Address CustomerAddress; // 值對(duì)象
public IList<OrderItem> Items; // 實(shí)體集合
}
// 實(shí)體
public class Address
{
public string ProductId; // 實(shí)體的主鍵,Order內(nèi)唯一即可
public string ProductName; // 值對(duì)象
public float Price; // 值對(duì)象
public int Count; // 值對(duì)象
}
// 值對(duì)象
public class OrderItem
{
public string Province; // 值對(duì)象
public string City; // 值對(duì)象
public string County; // 值對(duì)象
}
}
實(shí)體
有時(shí),實(shí)體并不見(jiàn)得是一種適當(dāng)?shù)慕9ぞ咝呛纾覀儗?duì)實(shí)體的使用也有可能是不恰當(dāng)?shù)牧闾А:芏鄷r(shí)候,一個(gè)領(lǐng)域概念應(yīng)該建模成值對(duì)象宽涌,而不是實(shí)體對(duì)象平夜。
由于只從數(shù)據(jù)出發(fā),CRUD系統(tǒng)是不能創(chuàng)建出好的業(yè)務(wù)模型的护糖。
值對(duì)象可以用于存放實(shí)體的唯一標(biāo)識(shí)褥芒。值對(duì)象是不變(immutable)的,這可以保證實(shí)體身份的穩(wěn)定性嫡良,并且與身份標(biāo)識(shí)相關(guān)的行為也可以得到集中處理锰扶。
添加業(yè)務(wù)邏輯
根據(jù)DDD,實(shí)體是能夠處理業(yè)務(wù)邏輯的寝受,應(yīng)該盡量將業(yè)務(wù)體現(xiàn)在實(shí)體上坷牛;如果某些業(yè)務(wù)牽涉到多個(gè)實(shí)體,無(wú)法將其歸結(jié)到某個(gè)實(shí)體的話(huà)很澄,就需要引入領(lǐng)域服務(wù)(Domain Service)京闰。案例業(yè)務(wù)簡(jiǎn)單,目前不會(huì)涉及到領(lǐng)域服務(wù)甩苛,因此蹂楣,在本案例中,業(yè)務(wù)邏輯都是在實(shí)體上處理的讯蒲。
以讀者(Reader)為例痊土,它有借書(shū)和還書(shū)的行為,我們將這兩種行為實(shí)現(xiàn)如下:
Reader中的業(yè)務(wù)邏輯
public partial class Reader : IAggregateRoot
{
public void Borrow(Book book)
{
if (book.Lent)
throw new InvalidOperationException("The book has been lent.");
Registration reg = new Registration();
reg.RegistrationStatus = RegistrationStatus.Normal;
reg.Book = book;
reg.Date = DateTime.Now;
reg.DueDate = reg.Date.AddDays(90);
reg.ReturnDate = DateTime.MaxValue;
book.Registrations.Add(reg);
book.Lent = true;
this.Registrations.Add(reg);
}
public void Return(Book book)
{
if (!book.Lent)
throw new InvalidOperationException("The book has not been lent.");
var q = from r in this.Registrations
where r.Book.Id.Equals(book.Id) &&
r.RegistrationStatus == RegistrationStatus.Normal
select r;
if (q.Count() > 0)
{
var reg = q.First();
if (reg.Expired)
{
// TODO: Reader should pay for the expiration.
}
reg.ReturnDate = DateTime.Now;
reg.RegistrationStatus = RegistrationStatus.Returned;
book.Lent = false;
}
else
throw new InvalidOperationException(string.Format("Reader {0} didn't borrow this book.",
this.Name));
}
}
倉(cāng)儲(chǔ)
在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的案例中墨林,倉(cāng)儲(chǔ)的設(shè)計(jì)是很具有爭(zhēng)議性的話(huà)題赁酝,因?yàn)閭}(cāng)儲(chǔ)這個(gè)角色本身就與領(lǐng)域模型和基礎(chǔ)結(jié)構(gòu)層對(duì)象相關(guān),它需要序列化領(lǐng)域?qū)ο螅☉?yīng)該說(shuō)是聚合)旭等,然后將其保存到基礎(chǔ)結(jié)構(gòu)層的持久化機(jī)制酌呆。于是,在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的社區(qū)中搔耕,存在兩種觀點(diǎn):
領(lǐng)域模型不能訪(fǎng)問(wèn)倉(cāng)儲(chǔ)隙袁,理由是:倉(cāng)儲(chǔ)需要跟技術(shù)架構(gòu)層打交道,在領(lǐng)域模型中訪(fǎng)問(wèn)倉(cāng)儲(chǔ)就會(huì)破壞領(lǐng)域模型的純凈度。需要使用倉(cāng)儲(chǔ)的藤乙,需要在領(lǐng)域模型上加上一層猜揪,比如Application層,在該層中獲取倉(cāng)儲(chǔ)實(shí)例并處理持久化邏輯
領(lǐng)域模型可以訪(fǎng)問(wèn)倉(cāng)儲(chǔ)坛梁,但僅僅是通過(guò)倉(cāng)儲(chǔ)接口和IoC容器訪(fǎng)問(wèn)倉(cāng)儲(chǔ)而姐;倉(cāng)儲(chǔ)的具體實(shí)現(xiàn)通過(guò)IoC注入到領(lǐng)域模型中
其實(shí),這只不過(guò)是個(gè)人習(xí)慣問(wèn)題划咐,我認(rèn)為兩種方法都可以接受拴念,具體采用哪種方法,就要看具體項(xiàng)目的需求和實(shí)現(xiàn)情況而定褐缠。在Tiny Library中政鼠,由于業(yè)務(wù)簡(jiǎn)單,所以采取的是第一種方式队魏,但上述的理由并不充分公般,換句話(huà)說(shuō),出于業(yè)務(wù)需求胡桨,我采用了第一種方式官帘,但并不是因?yàn)閭}(cāng)儲(chǔ)需要跟技術(shù)架構(gòu)層打交道所以才把對(duì)倉(cāng)儲(chǔ)的訪(fǎng)問(wèn)放在Application層中。倉(cāng)儲(chǔ)也是領(lǐng)域模型的一部分昧谊,領(lǐng)域模型依賴(lài)于倉(cāng)儲(chǔ)的抽象刽虹。