天天看點

DDD—實體和值對象

一、實體

     實體是領域模型中的一個對象,帶有業務含義的對象,集多個業務屬性,業務行為于一體。領域模組化時,我們根據業務場景分析,找到跟業務邏輯相關的實體對象,然後按照實體間的關聯将多個對象進行聚合。

     實體最大的特點是擁有唯一辨別符,這個辨別符貫穿整個軟體的生命周期,不随業務流程和狀态變更後更改,在領域模型中以領域對象DO的形式存在。

     如下代碼示例:人事領域裡的人員考勤子域,考勤裡面有員工對象,員工需要通過上班打卡,下班打卡完成一個完整的考勤行為,這個員工就是一個實體,因為每個人在公司都有工号,是辨別這個人的唯一ID,不管他去上海還是北京出差打卡,他的員工ID都不會變化。

  同時實體還會包含跟考勤相關的上班打卡和下班打卡方法,是一個充血模型。

二、值對象

   如上代碼示例中,員工的資訊由人員id,姓名,所在的省,市,街道組成,我們可以将省,市,街道抽象出來一個Address,構成一個位址的屬性集合,這個集合的名稱就是位址值對象。

   是以值對象就是一個屬性集合,将不同的關聯屬性組合成了一個概念整體,具有整體概念和不可修改的特性。

三、實體和值對象

   實體和值對象都是微服務底層最基礎的領域對象,實作領域最基本的業務邏輯。

   實體和值對象都是若幹屬性的集合,實體一般是帶有業務含義的的對象,具有業務屬性,業務行為和業務邏輯。

   值對象也是若幹屬性的集合,但他隻有資料初始化操作,不涉及資料修改,基本不包含業務邏輯。

      值對象是屬于實體的一部分,如果值對象是單一屬性,則直接定義為實體的屬性,如果值對象是屬性集合,則将他設計為值對象類,值對象沒有ID,會被實體整體引用。

DDD—實體和值對象

四、實體和值對象的資料庫形态

  實體:将領域模型映射到資料模型時,一個實體(DO)可能對應0,1,多個資料庫持久化對象(PO),大多數情況下DO和PO是一對一的關系,當DO隻是暫住記憶體時,也可以不需要持久化。當使用者和角色兩個實體合并成權限實體時,一個DO則對應了多個PO。當使用者實體和訂單實體合并到一張表中時,這兩個DO的生成需要一個PO的拆分,這是多對一的關系。

  值對象:值對象的資料庫設計大都采用了反範式,實體對象的屬性值和值對象的屬性值集合以JSON形式儲存在同一個資料庫表中。

  上面的人員和位址的對應關系,設計資料庫表時可以有三種方式:

  (1)把位址值對象的所有屬性加到人員實體表中:這種設計方式會破壞位址的業務含義和屬性完整

   

DDD—實體和值對象

  (2)建立人員實體的表,關聯位址實體表:增加了不必要的實體和表

    

DDD—實體和值對象

  (3)實體對象的屬性值和值對象的屬性值集合以JSON形式儲存在同一個資料庫表中:資料模組化時,可以将值對象的屬性集合嵌入到實體表中,保留對象的業務含義,同時減少了表的設計,一般是将值對象序列化成大對象JSON串後,嵌入到實體表中的

     

DDD—實體和值對象

  基于第三種方式的領域模型落地,現在許多資料庫都開始支援基于JSON串的CRUD操作了,當然,類似這樣值對象的設計,多了可能會使實體堆積一堆缺乏完整意義的屬性。

  但是值對象不可變,在并發環境下擷取的永遠是相同的對象,不會被修改,是以值對象可以被多個實體并發引用,是以高并發場景下的領域對象一般優先設計為值對象而非實體,可以保證線程安全。

  值對象還有一個重要作用,以資料備援的方式記錄業務發生那一刻的資料,比如使用者聚合中包含了使用者(聚合根,也是實體)和位址值對象,訂單生成那一刻,使用者和位址資訊以JSON的方式備援進訂單資料中,這樣避免了訂單聚合每次都通過調用使用者的聚合根ID擷取最新使用者資訊和位址,解耦了使用者聚合和訂單聚合,同時使用者實體和位址值對象也以備援的方式,記錄了業務的快照資料,還原業務發生前後的場景。

五、貧血模型和充血模型

    貧血模型:Spring的bean就是一種貧血模型,領域被用來作為屬性存儲的載體,而沒有實作具體方法,比如教育領域的校長,隻會記錄他的年齡,職級,工作年限,而不會有他的一些行為的方法實作,單單用來作為屬性的存儲。

    充血模型:實體不止包含對象的屬性,也包含他對應的行為的方法實作,而不止僅僅作為屬性存儲的載體。