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


一、前言

    上一篇我們講了DDD的核心概念(附上鏈接),並且設計了我們的上下文映射圖,那么接下來就准備開始立項了,本篇文章的部分知識點可能對一部分人來說比較基礎,可以選擇性的閱讀。

    在這之前我們平常用的最多的應該就是3層架構了,這里也不展開描述了,大家都是在3層的陪伴下一路走來的~

    DDD所使用的傳統分層架構是松散分層,也就是上層可以訪問任意層級的下層,而不是僅限於當前層的下一層,這是有別於3層架構的。如下面2張圖的區別圖:

      

                        【圖1】

  

                        【圖2】

Application:這層的職責是對接收到的數據做一些非業務性驗證,事務的控制,最重要的是協調多個聚合之間的操作。這里應該可以清晰的表達出整個操作所做的事情,並且與通用語言是一致的。

Domain:這一層是DDD設計的核心,這里不但需要精確合理的表達出通用語言的每一個細節,另外如何把對象合理的定義為聚合、實體、值對象也是重中之重。這里不但關系着整個項目的復雜度,也是戰術建模的體現,任何的一行代碼都是對業務的准確定義,應該是恰到好處。一個清晰簡潔的戰術建模才可以應對后續的快速變化。

Infrastructure:這里是輔助性的一層,也是整個項目的基礎。好比這里存放着一磚一瓦,最終建造什么模樣的高樓在於用它的地方。主要包括,倉儲的實現(我們存放數據的地方)、一些通用的支撐性類庫。

 

二、六邊形架構

    在[Vaughn Vernon]《實現領域驅動設計》一書中多次提到對DDD主張六邊形架構的概念,六邊形架構對於保證限界上下文內的領域概念的清晰性有着重要的作用,那么什么是六邊形架構,如下圖3(摘自[Vaughn Vernon]《實現領域驅動設計》一書)。

  

                      【圖3】

    在當今越來越提倡開放合作的大環境下,引用的多樣化的Service,和在自身系統達到一定規模之后的分布式治理,越來越需要通過協作進行工作,那么如何提升協作的效率變得越來越重要。提高各個應用程序的自治性,是一種有效提升協作能力的手段。從上圖中看出為了保證領域模型所在的應用程序的干凈簡潔和自治性,各種適配器作為"防腐層(在上篇中有提到)"在整個程序的最外層保護着當前的“界限上下文(在上篇中有提到)”不受外部入侵。

    所以在我們的整個設計中需要注意對涉及到外部系統交互的地方的抽象,通過面向接口、依賴注入等方式做到外部的變化對自身系統的影響最小化。

 

三、終於開始建項目了

    按照之前的這些描述,我們終於初步建立了我們的解決方案。如下圖4:

  

                【圖4】

這里把每個項目的職責大致說一下。

Mall:負責我們的電商網站的界面處理和用戶的數據錄入

Mall.Application:按模塊分別定義不同的ApplicationService來講述每一個操作下的“故事”。

Mall.Application.DomainEventSubscribers:所有的領域事件訂閱者。

Mall.Domain:這里存放着戰術建模的結晶,Entity、Aggregate、ValueObject。(下面會具體講述下)

Mall.Domain.Events:所有的領域事件,這層也可以合並到Domain中,給它新建一個文件夾。

Mall.Domain.IRepositories:所有的倉儲(資源庫)接口。類似於三層中的IDAL。

Mall.DomainService:領域服務,存放着那些不適合放在聚合/值對象上的無狀態的操作方法,用於實現特定某個領域的任務。

Mall.Infrastructure:存放着一些通用類庫

Mall.Infrastructure.Repositories:所有倉儲(資源庫)的實現。

Mall.Infrastructure.Translators:翻譯層,也就是與外部系統溝通的橋梁,主要的職責就是做好“反腐層”的重任。

 

四、DDD中的3個臭皮匠

    這里的3個臭皮匠其實就是:Entity、ValueObject、Aggregate。我們要提煉出業務中的精華,合理的抽象為這3個概念,並且這種抽象是需隨着領域里的概念變化而變化的。這3者的結合運用會讓我們的項目活起來,這是DDD的核心。這里再把這3個概念重新梳理一下。

    Entity(實體): 每個實體是唯一的,並且可以相當長的一段時間內持續地變化。我們可以對實體做多次修改,故一個實體對象可能和它先前的狀態大不相同。但是,由於它們擁有相同的身份標識,他們依然是同一個實體。

    ValueObject(值對象):值對象用於度量和描述事物,當你只關心某個對象的屬性時,該對象便可作為一個值對象。實體與值對象的區別在於唯一的身份標識和可變性。

    Aggregate(聚合):聚合類是實體的升級,是由一組與生俱來就密切相關實體和值對象組合而成的,這整個組合的最上層實體就是聚合。

 

五、CQRS(Command Query Responsibility Segregation)

    說到DDD必然要提一下CQRS,我認為CQRS和DDD的關系就像咖啡和牛奶,給大型系統的構建提供了一劑良葯,它生於讀寫分離,具有高吞吐量、高伸縮性等特點,值得我們為之付出一些代價。但是CQRS的使用會使整個數據持久化和查詢的鏈路拉長,並且工作量也會比簡單的讀寫一體化大的多,所以需要對項目做出合理的考量來決定是否使用。

    當我們需要把某個復雜的聚合修改之后寫入到數據庫的時候,要保證N張表的數據被同時修改成功,整個事務的周期必然會加長。而且當我們需要顯示來自不同聚合類型與實例的數據時,我們的SQL必然包含N多的join。領域越復雜這種情況越發常見。

    CQRS需要和事件源結合使用,對數據的修改操作只是往事件源里增加一條修改后的結果記錄(類似於我們的源碼控制軟件的log),並不會直接把修改后的對象持久化到數據庫。這樣能夠大大提高數據修改的速度,並且對於查詢操作的實現方式就比較多樣化了。大致列舉了以下4種方式:

    1.還是使用單個數據庫,每次領域對象的獲取都需要根據事件源中的事件集合做重建,得到當前的最新的數據返回。這只是編碼設計上的讀寫分離

    2.拆分為讀庫和寫庫,實現方式同1

    3.拆分為讀庫和寫庫,並且針對讀庫做專門的查詢數據冗余,異步的通過事件源來修改查詢數據,可以結合merge commit。

    4.在3的基礎上做讀庫的負載均衡

    這4種方式復雜度各不相同,可以結合實際項目的復雜度擇優選擇,其中最關鍵的一條便是是否存在大量的列表類數據展示,如果是那么1和2便就不適合了。在以上的方式之外可以結合其他的數據存儲一起使用,如緩存,NoSql,然而這只需要訂閱所有的命令事件即可實現。

 

六、結語

    本篇主要介紹了項目的分層架構、每層的職責和里面存放什么樣子的類。限於非我們這個系列的核心主題,所以都沒有發散出去做更加具體形象的描述,希望大家可以邊結合[Vaughn Vernon]《實現領域驅動設計》一書的閱讀跟着我做實際的編碼來加深對DDD的理解。跳出根深蒂固的三層思想是痛苦的,但是我認為只要堅持下去,DDD會讓你看見一片世外桃源,到那時會覺得我們的付出都是值得的。並且DDD思想的運用可大可小,小到類的設計,大到復雜項目之間的構架,都會給你提供幫助。

 

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

 

 

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

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

 

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

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


免責聲明!

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



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