DDD理論學習系列(6)-- 實體

DDD理論學習系列——案例及目錄


1.引言

實體對應的英語單詞為Entity。提到實體,你可能立馬就想到了代碼中定義的實體類。在使用一些ORM框架時,比如Entity Framework,實體作為直接反映數據庫表結構的對象,就更尤為重要。特別是當我們使用EF Code First時,我們首先要做的就是實體類的設計。在DDD中,實體作為領域建模的工具之一,也是十分重要的概念。

但DDD中的實體和我們以往開發中定義的實體是同一個概念嗎?
不完全是。在以往未實施DDD的項目中,我們習慣于將關注點放在數據上,而非領域上。這也就說明了為什么我們在軟件開發過程中會首先做數據庫的設計,進而根據數據庫表結構設計相應的實體對象,這樣的實體對象是數據模型轉換的結果。
在DDD中,實體作為一個領域概念,在設計實體時,我們將從領域出發。

2.DDD中的實體

DDD中要求實體是唯一的且可持續變化的。意思是說在實體的生命周期內,無論其如何變化,其仍舊是同一個實體。唯一性由唯一的身份標識來決定的。可變性也正反映了實體本身的狀態和行為。

3. 唯一標識

舉個例子,在有雙胞胎的家庭里,家人都可以快速分辨開來。這得益于家人對雙胞胎性格和外貌的區分。然而鄰居卻不能,只能通過名字來區分。上小學后,學校里盡然有重名的,這時候就要取外號區分了。上大學后,要坐火車去學校,買票時就要用身份證號來區分了。

針對這個例子,如果我們要抽象出一個User實體,要如何定義其唯一標識呢?
其中性格、外貌、昵稱、身份證號都可以作為User實體的屬性,在某些場景下某個屬性就可以對對象進行區分。但為了確保標識的穩定性,我們只能將身份證號設為唯一身份標識。

3.1.唯一標識的類型

唯一標識的類型在不同的場景又有不同的要求。
主要可以分為有意義和無意義兩種。
在一個簡單的應用程序里,一個int類型的自增Id就可以作為唯一標識。優點就是占用空間小,查詢速度快。
而在一些業務當中,要求唯一標識有意義,通過唯一標識就能識別出一些基本信息,比如支付寶的交易號,其中就包含了日期和用戶ID。這種就屬于字符串類型的標識,這就對唯一標識的生成提出了挑戰。
在一些復雜的業務流程中,對唯一標識沒有要求,我們可以使用GUID類型來生成唯一標識,很顯然GUID占用空間就畢竟大,且不利于查詢。

3.2.唯一標識的生成時機

有某些場景下,唯一標識的生成時機也各不相同,主要分為即時生成和延遲生成。
即時生成,即在持久化實體之前,先申請唯一標識,再更新到數據庫。
延遲生成,即在持久化實體之后。

3.3.委派標識和領域標識

基于領域實體概念分析確定的唯一身份標識,我們可以稱為領域實體標識。
而在有些ORM工具,比如Hibernate、EF,它們有自己的方式來處理對象的身份標識。它們傾向于使用數據庫提供的機制,比如使用一個數值序列來生成識。在ORM中,委派標識表現為int或long類型的實體屬性,來作為數據庫的主鍵。很顯然,委派標識是為了迎合ORM而創建的,且委派標識和領域實體標識無任何關系。

那既然ORM需要委派標識,我們就可以創建一個實體基類來統一指定委派標識。而這個實體基類又被稱為層超類型。

3.3.1.實現層超類型

首先定義層超類型接口:

public interface IEntity
{
}

public interface IEntity<TPrimaryKey> : IEntity
{
    TPrimaryKey Id { get; set; }
}

通過定義泛型接口,以支持自定義主鍵類型。

實現層超類型:

    public class Entity : Entity<int>, IEntity
    {
    }

    public class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
    {
        public virtual TPrimaryKey Id { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Entity<TPrimaryKey>))
            {
                return false;
            }

            //Same instances must be considered as equal
            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            var other = (Entity<TPrimaryKey>) obj;
            //Must have a IS-A relation of types or must be same type
            var typeOfThis = GetType();
            var typeOfOther = other.GetType();
            if (!typeOfThis.GetTypeInfo().IsAssignableFrom(typeOfOther) && !typeOfOther.GetTypeInfo().IsAssignableFrom(typeOfThis))
            {
                return false;
            }

            return Id.Equals(other.Id);
        }

        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        public static bool operator ==(Entity<TPrimaryKey> left, Entity<TPrimaryKey> right)
        {
            if (Equals(left, null))
            {
                return Equals(right, null);
            }

            return left.Equals(right);
        }

        public static bool operator !=(Entity<TPrimaryKey> left, Entity<TPrimaryKey> right)
        {
            return !(left == right);
        }
    }

可以看到默認的委托標識為int類型。我們重寫了Equals,GetHashCode方法,以及==和!=兩個操作符。

通過這樣一種方式,我們進行約定,所有的實體必須繼承自Entity,即可實現委托標識的統一定義。

4.可變性

解決了實體的唯一身份標識問題后,我們就可以保證其生命周期中的連續性,不管其如何變化。

那可變性說的是什么呢?可變性是實體的狀態和行為。
而實體的狀態和行為就要對具體的業務模型加以分析,提煉出通用語言,再基于通用語言來抽象成實體對應的屬性或方法。

我們拿訂單環節來舉例說明:
當顧客從購物車點擊結算時創建訂單,初始狀態為未支付狀態,支付成功后切換到正常狀態,此時可對訂單做發貨處理并置為已發貨狀態。當顧客簽收后,將訂單關閉。

從以上的通用語言的描述中(在通用語言的術語中,名詞用于給概念命名,形容詞用于描述這些概念,而動詞則表示可以完成的操作。)
我們可以提取訂單的相關狀態和行為:

  • 訂單狀態:未支付、正常、已發貨、關閉。針對狀態,我們需定義一個狀態屬性即可。
  • 訂單的行為:支付、發貨和關閉。針對行為,我們可以在實體中定義方法或創建單獨的領域服務來處理。

實體既然存在狀態和行為,就必然會與事件有所牽連。比如訂單支付成功后,需要知會商家發貨。這時我們就要追蹤訂單狀態的變化,而追蹤變化最實用的方法就是領域事件。關于領域事件,我們后續再講。

5.實體的驗證

驗證的目的是為了檢查模型的正確性和有效性。檢查的對象可以為某個屬性,也可以是整個對象,或是多個對象的組合。針對驗證的方式,不一而足,根據需要可自行發揮。

6.總結

實體作為領域建模的工具之一,唯一的身份標識是實體最基本的特征,其次是可變性。唯一身份標識和可變性也是用來區分實體和值對象的主要特征。

為了正確建立實體模型,我們需要將關注點從數據轉向領域,從業務模型中提煉通用語言,再基于通用語言分析其狀態和行為。

所以,我們可以認為:實體 = 唯一身份標識 + 可變性【狀態(屬性) + 行為(方法或領域事件或領域服務)】

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容