如何一步一步用DDD設計一個電商網站(九)—— 小心陷入值對象持久化的坑


本系列所有文章

如何一步一步用DDD設計一個電商網站(一)—— 先理解核心概念

如何一步一步用DDD設計一個電商網站(二)—— 項目架構

如何一步一步用DDD設計一個電商網站(三)—— 初涉核心域

如何一步一步用DDD設計一個電商網站(四)—— 把商品賣給用戶

如何一步一步用DDD設計一個電商網站(五)—— 停下腳步,重新出發

如何一步一步用DDD設計一個電商網站(六)—— 給購物車加點料,集成售價上下文

如何一步一步用DDD設計一個電商網站(七)—— 實現售價上下文

如何一步一步用DDD設計一個電商網站(八)—— 會員價的集成

如何一步一步用DDD設計一個電商網站(九)—— 小心陷入值對象持久化的坑

如何一步一步用DDD設計一個電商網站(十)—— 一個完整的購物車

如何一步一步用DDD設計一個電商網站(十一)—— 最后的准備

如何一步一步用DDD設計一個電商網站(十二)—— 提交並生成訂單

如何一步一步用DDD設計一個電商網站(十三)—— 領域事件擴展

 

 

閱讀目錄

 

一、前言

  在上一篇中(如何一步一步用DDD設計一個電商網站(八)—— 會員價的集成),有一行注釋的代碼:

    public interface IRoleDiscountRelationRepository// :    IRepository<RoleDiscountRelation>
    {
        RoleDiscountRelation Get(string roleId);
    }

  其中涉及的到問題是關於值對象的持久化問題。是的,由於我們之前的設計中持久化是僅針對聚合根的:

    public interface IRepository<T> where T : AggregateRoot
    {
        string NextIdentity();

        void Save(T aggregate);

        T GetByIdentity(string identity);
    }

  但是有時候難免會遇到一些需要持久化值對象的場景:

  場景1:一些不屬於任何聚合根的對象,本身又可以當作一個不可變的值來看待(如省市區信息等),當然的確某個地區改名了可以作為一個新的值對象來表示。那么我們在把它們建立為值對象的同時,又需要持久化到數據庫。這里就如這個等級折扣。

  場景2:一個聚合根的內部引用了一個值對象的集合,那么如果使用的是關系型數據庫進行存儲,必然需要單獨存一個表。

  其中場景1正好是我們Demo里遇到的場景,下面來一起闡述下我對這2個場景的理解和處理方式。

 

二、場景1的思考

  整個問題的解決方式,首先需要梳理清楚3個基本概念:“聚合根”、“實體”、“值對象”這3者的關系。這個我在(如何一步一步用DDD設計一個電商網站(二)—— 項目架構)中有提及。因為涉及到持久化,所以我們可以再通過分析這3種對象的生命周期來幫助思考。

  聚合根:獨立存在的對象,是代表某個限界上下文中的一個高內聚的整體概念。他的生命周期是其所屬上下文中所承擔的職責的周期。

  實體:無法獨立存在,是聚合根的一部分,生命周期由所屬的聚合根掌控。

  值對象:可以獨立存在,但是無法進行自我管理,可以描述任何聚合根/實體,無生命周期的概念,也可以理解為永生(無限生命周期)。

  把任何一個復雜的事物化繁為簡的方式就是不斷的提煉,歸約。動靜分離就是歸約的一種方式,筆者我認為在DDD中“動”就是聚合根和實體,“靜”就是值對象,如果能不斷的提煉出“靜”的部分對於整個領域的理解復雜度是有幫助的。“動”是復雜的“似生命體”,是有壽命的;“靜”是簡單的“死物”,它一直存在,而且一直在那里。

  因此筆者我認為只要是可獨立存在的對象都可以使用Repository來持久化。那么我們的Demo中,既然已經決定將等級和折扣率建立為值對象的話,接下去的持久化要怎么做呢?請看Part Ⅳ。

 

三、場景2的思考

  場景2里有一個比較容易踩進去的坑,為了持久化把原本設計成值對象的改為實體(特別是針對一個值對象的集合的時候,需要一個唯一表示來區分其中多個值對象)。特別特別注意,當在腦海中出現這個意識的時候,需要在思維上保證從領域建模的角度思考,而不是為了持久化。因為實體建模是一種數據化的建模方式,很大程度上收到了數據庫范式的影響。這里引用[Vaughn Vernon]《實現領域驅動設計》中的4個問題:

  1.我們當前建模的概念表示領域中的一個東西呢,還是只是用於描述和度量其它東西?

  2.如果該概念起描述作用,那么它是否滿足以下幾大特征?

    ①它度量或者描述了領域中的一件東西。
    ②它可以作為不變量。
    ③它將不同的相關的屬性組合成一個概念整體。
    ④當度量和描述改變時,可以用另一個值對象予以替換。
    ⑤它可以和其他值對象進行相等性比較。
    ⑥它不會對協作對象造成副作用。

  3.將該概念建模成實體是不是只是持久化機制上的考慮?

  4.將該概念建模成實體是不是因為它擁有唯一標識,我們關注的是對象實例的個體性,並且需要在其整個生命周期中跟蹤其變化?

  如果你的答案是“描述,是,是,不是”,那么此時你應該堅持用值對象。我們不應該讓持久化影響到領域對象的建模。

  那么我們該怎么做呢?請看Part Ⅳ。

 

四、避坑方式

  數據庫選型上在Nosql出現后,可以很好的避免了這里的持久化問題。但是弱化的關系之后導致一些查詢問題需要做更多的工作去解決。並且我認為在業務復雜的電商系統中,用關系型數據庫作為最終的技術選型還是最常見的一種方式。那么在使用關系型數據庫的情況下,我們可以通過使用以下幾種方式解決這個問題:

  1.把值對象中的屬性作為所屬實體/聚合根的數據列來存儲。

    缺點:會導致數據表列數較多,在一個數據頁存儲的數據量變少,影響數據庫表的使用性能。

  2.把整個值對象序列化后作為所屬實體/聚合根的數據列來存儲。

    缺點:出現大數據長度的列,頁會導致在一個數據頁存儲的數據量變少,影響數據庫表的使用性能。另外無法直接通過SQL來查詢值對象的屬性,需要自定義做反序列化操作。

  3.如果是ORM類的框架,那么建立一個基類通過protected的標識列來委派這個唯一標識,但是在實際運用過程中是感覺不到這個存在的。

  4.如果不是ORM框架或者本身框架支持,那么可以通過無主鍵的方式存入到數據表中。

    缺點:是無法嵌套模型。

 

五、實踐

  我想上面說的4種方式中的1、2、4都比較好理解,所以在我們的Demo中,我准備使用第3種方式來處理當前的值對象持久化。先看下我們當前抽象出來的幾個核心類。

    public abstract class ValueObject
    {
    }
    public abstract class Entity
    {
    }
    public abstract class Aggregate : Entity
    {
    }

  簡單的不能再簡單的,僅僅起到了一個表示作用,但是也體現出了這3個對象之間的聯系。根據本篇講述的內容,再進行一次進行抽象。變成如下圖1所示的一個關鍵模型。

                            【圖1】

   其中需要注意的是Entity中的ID其實就是對DelegateIdentifier.Identity的引用,也就是僅僅為了做一個Public的公開。另外AloneStorableValueObject與ValueObject唯一不同是其需要持久化並獨占一個數據表,而ValueObject是不需要持久化或者跟着所屬的聚合根持久化的。然后我們的IRepository<T>變成下面這樣約束:

    public interface IRepository<T> where T : DelegateIdentifier, IAloneStorable
    {
        string NextIdentity();

        void Save(T aggregate);

        T GetByIdentity(string identity);
    }

  這樣只有繼承了AggregateRoot和AloneStorableValueObject可以有自己的Repository了。

 

六、結語

  從業務角度來說設計就是不斷的梳理業務再抽象建模的過程,不是一蹴而就,也沒有一招打遍天下的方式。需要不斷的演進、找到最適合的設計方式。從更泛角度來說設計也是約束、定義規則的過程,一套清晰的規則可以為整個項目的所有開發者往共同的目標前進起到事半功倍的效果。

 

 

 

本文的源碼地址:https://github.com/ZacharyFan/DDDDemo/tree/Demo9

 

作者:Zachary
出處:https://zacharyfan.com/archives/174.html

 

 

▶關於作者:張帆(Zachary,個人微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。歡迎掃描右側的二維碼~。

定期發表原創內容:架構設計丨分布式系統丨產品丨運營丨一些思考。

 

如果你是初級程序員,想提升但不知道如何下手。又或者做程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注我的公眾號「跨界架構師」,回復「技術」,送你一份我長期收集和整理的思維導圖。

如果你是運營,面對不斷變化的市場束手無策。又或者想了解主流的運營策略,以豐富自己的“倉庫”。歡迎關注我的公眾號「跨界架構師」,回復「運營」,送你一份我長期收集和整理的思維導圖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM