領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)案例-圖書(shū)館圖書(shū)管理

領(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ǔ)的抽象刽虹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呢诬,隨后出現(xiàn)的幾起案子涌哲,更是在濱河造成了極大的恐慌,老刑警劉巖尚镰,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阀圾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡狗唉,警方通過(guò)查閱死者的電腦和手機(jī)稍刀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)敞曹,“玉大人,你說(shuō)我怎么就攤上這事综膀“钠龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵剧劝,是天一觀的道長(zhǎng)橄登。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么拢锹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任谣妻,我火速辦了婚禮,結(jié)果婚禮上卒稳,老公的妹妹穿的比我還像新娘蹋半。我一直安慰自己,他們只是感情好充坑,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布减江。 她就那樣靜靜地躺著,像睡著了一般捻爷。 火紅的嫁衣襯著肌膚如雪辈灼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天也榄,我揣著相機(jī)與錄音巡莹,去河邊找鬼。 笑死甜紫,一個(gè)胖子當(dāng)著我的面吹牛降宅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棵介,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钉鸯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了邮辽?” 一聲冷哼從身側(cè)響起唠雕,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吨述,沒(méi)想到半個(gè)月后岩睁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揣云,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年捕儒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邓夕。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刘莹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焚刚,到底是詐尸還是另有隱情点弯,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布矿咕,位于F島的核電站抢肛,受9級(jí)特大地震影響狼钮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捡絮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一熬芜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧福稳,春花似錦涎拉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至略板,卻和暖如春毁枯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叮称。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工种玛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓤檐。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓赂韵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親挠蛉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祭示,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容