消息場景:用戶 A 發送一個消息給用戶 B,用戶 B 回復一個消息給用戶 A。。。
現有設計:消息設計為實體並為聚合根,發件人、收件人設計為值對象。
三個問題:
- 實體最重要的特性是什么?
- Message 實體是怎么得來的?
- 發件人、收件人為什么不是實體?
1. 實體最重要的特性是什么?
《領域驅動設計》5.2 實體:
摘錄一段:許多對象不是由它們的屬性來定義,而是通過一系列的連續性(continuity)和標識(identity)來從根本上定義的。
歸納:
- 標識(identity)
- 連續性(continuity)
標識在實體中的另一種體現就是唯一和不可變,其概念在很多資料中有說明,這也是實體最重要的特性。
我有一個雙胞胎哥哥,我們倆出生的時候,長得一模一樣,以至於我們的爸媽都分不清,不得已他們在我們脖子上系個項鏈來標記:誰是老大?誰是老二?其實這個“標記”就可以看作是實體的標識,只不過是用項鏈來標識的,就像我們在項目中使用 GUID 方式一樣,目的就是用來體現標識,但不管用什么方式表示,這個標識必須在這個特定環境下唯一,也就是說,我和我雙胞胎哥哥的項鏈不能完全一樣,要不然我爸媽就不能區分我們倆了。
我和我那雙胞胎哥哥就這樣一天一天的長大,但出奇的是,我們哥倆越長越像,以至於我們互相看對方,都以為自己在“照鏡子”一樣,但唯一不變的是我們倆脖子上的項鏈,這也是區分我們哥倆的唯一方式。剛出生的我和現在的我,脖子上的項鏈是一樣的,這也就是實體標識的不可變性,也就是說剛出生的我和現在的我是同一個人,項鏈只不過在我成長的過程中起到“標記”的作用(當然也可以是手帶、腳環之類的信物),它會“陪伴”我的一生,這個“陪伴”的過程,可以理解為實體的另一種特性-連續性。
有一天,我們鎮要統計雙胞胎的分布情況,然后調查人員來到我們家,問我們爸媽:“你們家里有沒有雙胞胎?幾對雙胞胎?龍鳳胎?還是。。。”,然后我爸媽就報上:“一對雙胞胎-兩個小子”,然后調查人員就做了筆記走了。在這個過程中,他們絲毫沒有提及我脖子上的“項鏈”,雖然它在我爸媽眼里是那么重要(用來標記我們哥倆),但在調查人員眼里卻什么都不是,他們只需要知道我和我雙胞胎哥哥是什么樣的雙胞胎就行了,這也就是實體和值對象的根本區別:實體不僅需要知道它是什么?而且還需要知道它是哪個?而值對象只需要知道它是什么?
特定環境下,實體和值對象的區分例子有很多,比如《領域驅動設計》書中所說的“體育場座位例子”和“ Custorm-Address 例子”等等,但大部分都是強調實體的標識特性,卻很少提及連續性,那什么是連續性?這部分內容,在《領域驅動設計》中5.2實體章節中最后部分有提及,但都是零碎的概念性文字,如果不注意的話,很容易會被忽略掉。
摘錄一段:只要一個對象在生命周期中能夠保持連續性,並且獨立於它的屬性(即使這些屬性對系統用戶非常重要),那它就是一個實體。
這個內容可以結合上面我和我雙胞胎哥哥的例子進行理解,“項鏈”會陪伴的我一生,這段話可以拆分對應理解:項鏈-標識、一生-生命周期、陪伴-連續性。也就是說連續性不能理解為生命周期,它應該理解為:標識在實體生命周期內體現出連續性。
2. Message 實體是怎么得來的?
結合上面實體特性的理解,Message 實體是怎么得來的,就很好理解了,消息場景毫無疑問聚合的是消息,消息實體是怎么得來的?可以換個角度理解:為什么把消息設計為實體?首先看下消息實體符不符合實體的兩個特性。
-
標識(identity):消息場景中消息的區分通過什么?標題?內容?這些都不行,為了保證消息的唯一性,必須使用標識進行區分,而且必須不可變。消息場景中,有可能會出現標題和內容一樣的消息,但這卻不是同一個消息,就像我和我那雙胞胎哥哥,長的一樣,卻不是同一人,可以這樣說:標識的作用就是為了區分,而消息也必須要區分,所以。。。
-
連續性(continuity):一次我們家吃飯的時候,我一不小心把飯碗給打碎了,然后我媽就痛打了我一頓,她有個做筆記的習慣,記錄我們哥倆的日常生活,比如這次需要記錄一下:今天打了誰?但當時她打完我之后,卻不記得是打了我?還是我哥?然后她就挨個看我們的屁股和項鏈,來確定今天打了誰?這就是標識在生命周期中連續性的部分體現。消息場景中,在某一階段需要對消息進行處理,這個處理需要通過標識來明確處理的是哪條消息?這個對消息處理過程的體現就是連續性,有時候連續性需要在標識明確的情況下,但還有一種是其自身的生命周期連續性,比如從消息的創建,到管理,再到最后的銷毀,這個過程就是消息實體的連續。
上面的分析說明消息實體符合實體的兩個特性,也就是說消息可以設計為實體,至於怎么得來的?可以這樣理解,消息場景首先考慮的是消息,就像我們家的雙胞胎,首先考慮的是我和我那雙胞胎哥哥。
3. 發件人、收件人為什么不是實體?
在之前的一篇博文中,園友鼻涕成詩有這樣的疑問:聯系人作為值對象這一點有點不太理解,好處是什么?我當時是這樣回復的:
聯系人作為值對象,因為他不在消息系統中存儲,是從外部獲取的,而且它的存在要依附於消息,在消息系統這個業務場景中,如果脫離了消息,它就沒有什么意義,對於消息而言,我只要知道這個聯系人的內容是什么就行了,而不需要它具體什么哪個,人?還是郵箱?這個它並不關心,不是說把聯系人作為值對象有什么好處,而是在這個業務場景下,這樣設計比較合理些。
回復內容現在看來有些牽強,先不討論對與錯,按照上面消息實體的分析模式,在消息場景下,看下發件人、收件人(可以統稱為聯系人,發件人和收件人有可能為同一聯系人)是否具有實體的一些特性。
-
標識(identity):聯系人是否具有標識?也就是說聯系人需不需要進行區分?答案當然是要進行區分,要不然收件箱、發件箱就沒辦法針對收件人、發件人進行標識,而且聯系人有可能名稱相同,但是兩個不同的聯系人,也就是說在消息的整個應用場景中,聯系人是必須要唯一標識的,不管它扮演的角色是發件人,還是收件人,這個“角色扮演”概念只是針對某一具體消息來說,聯系人所存在的意義(在這個消息中,這個聯系人是發件人,但在另外一個消息中,有可能是收件人),但相對於整個消息場景,這個聯系人標識是唯一的,而且是不可變的。
-
連續性(continuity):這個可能沒有消息實體的連續性好理解,聯系人的連續性其實是依附於消息實體而言,它如果獨立出來,自身在消息場景中,是沒有連續性概念的,就比如在創建消息的時候,我需要判斷收件人是否存在,存在的話就創建收件人對象,並賦予創建消息的收件人屬性,還有就是消息在被閱讀的時候,需要判斷閱讀人是否有閱讀權限等等,這一些操作,就體現出聯系人的連續性依附於消息實體,但不可否認,聯系人的創建、使用、舍棄等操作,都可以理解圍繞某一具體消息的生命周期,也就是聯系人的連續性,而且在這個過程中,聯系人的標識都需要首先被明確。
在之前的理解中,聯系人設計為值對象的想法是,把聯系人看作是一個值,一個依附於消息實體的具體值,我只需要知道這個值就行了,具體體現就是 SenderID 或 RecipientID,其實這個就是聯系人的標識,只是當時被兩點所迷惑:
- 聯系人外部存儲:在消息場景中,聯系人的獲取是從外部獲得的,也就是說聯系人不在消息場景中存儲,也不進行管理,只是一個獲取操作,這個和一般的實體場景不太一樣,但仔細一想,不管它是從哪里獲取的,這個不應該在消息場景中所關心,我應該專注於聯系人在消息場景中的連續性。
- 聯系人依附於消息:這個是最重要的迷惑點,或者說是我根本不了解實體和值對象到底應該是什么?聯系人獨立於消息,在消息場景中,沒有任何意義,但不能因為這一點,就把它設計為值對象,有很多實體是依附關系,只要它存在標識和連續性,那它就是實體。
把聯系人設計為值對象當然也有“好處”,比如可以減少對聯系人的管理,因為如果聯系人設計為值對象,那它就是一個值,也就沒有對象的概念,但出來混的遲早是要還的,我要加一個用戶禁言功能,這個在現有的設計中就不好進行實現。像這種依附性實體的場景也很多,比如購物車應用中的 Order 和 Custorm,Custorm 依附於 Order,這個首先需要明確的是購物車應用場景,如果是其他的場景下,那 Custorm 就不存在依附關系。
我和我雙胞胎哥哥出生的時候,在我們的保溫箱上,除了需要標明我們兩個的”身份“之外,還需要標明我們爸媽的”身份“,具體標識可以用身份證號,這個就像消息實體中的 SenderID、RecipientID 一樣,雖然它是一個”值“,但我還需要知道它具體標識的是哪個對象,因為我不僅需要它表示的值是多少,我還需要知道它所代表的對象是哪個,就比如我和我雙胞胎哥哥要根據這個身份證號,找到我們的父母一樣。
4. 發件人、收件人是值對象?還是實體?
話不言多,總之一句話:發件人、收件人(聯系人)需要設計為實體。
消息場景實體和值對象:
- Message 消息實體和 Contact 聯系人實體。
- 值對象若干(如 MessageState、MessageType 等)。
概念理解: