DDD方法中并沒有指定使用特定的架構。領域中的BC被封裝為高內聚的模塊,這種特性讓DDD對架構并沒有太大侵入性。架構可以應用于領域內部的結構,也可以包圍著領域模型,系統中可以采用多種風格的架構。
架構是指構成一個系統的主要元素及它們之間的主要關聯,這些元素和關聯能夠反映該系統的本質特征。
選擇架構應該了解架構的來源和所要解決的問題,從業務和問題出發,避免濫用架構。例如分布式應用的架構有REST、DO(分布式對象)和RPC,REST架構是Web風格的架構,而DO和RPC是面向企業應用的,技術和架構也不是一個概念,例如用了HTTP不一定就是Web架構。
以建筑為例,蘇州園林是一種建筑風格,歐洲的哥特式教堂也是一鐘建筑風格,在蘇州園林中放個哥特式教堂就讓讓人感覺畫風突兀。
分層架構
分層架構是一種歷史悠久的架構,通過分層架構,可以將系統按不同職責組織成有序層次,由于這種劃分往往比較容易界定,也算是最常見和最受歡迎的一種架構,有一個說法是:“如果你不知道要用什么架構,那就用它。
Fowler在《Patterns of Enterprise Application Architecture》中對分層的定義是:
當我們說一個系統是分層架構的時候,你可以把這個軟件想象成一個有很多層的蛋糕的樣子,其中每一層放在它的下一層上。較高層使用諸多較低層定義和提供的服務,但較低層并沒有察覺較高層的存在。另外,每一層都會對其上層隱藏更低的層。
介紹分層的文章和例子都比較多,不再贅述,簡要說下DDD中的分層模型。
在分層架構中,我們將領域模型和業務邏輯分離出來,并減少對基礎設施、用戶界面甚至應用層邏輯的依賴,因為它們不屬于業務邏。將一個復雜的系統分為不同的層,每層都應該具有良好的內聚性,并且只依賴于比其自身更低的層。[Evans, Ref, P16]
一個DDD中的分層架構可以如下圖所示,通過把領域層單獨分離出來,負責表達業務概念,業務狀態以及業務規則,形成對領域知識的集中并形成業務軟件的核心。
不過從分層架構圖中可以發現,將基礎設施層放入底層是存在缺點的,領域層依賴于基礎設施層,這對領域層的內聚性產生影響。一個解決方案就是依賴倒置。
依賴倒置
依賴倒置的原則(DIP)由Robert C. Martin提出,核心的定義是:
高層模塊不應該依賴于底層模塊,兩者都應該依賴于抽象
抽象不應該依賴于實現細節,實現細節應該依賴于接口
按照DIP的原則,領域層就可以不再依賴于基礎設施層,基礎設施層通過注入持久化的實現就完成了對領域層的解耦,采用依賴注入原則的新分層架構模型就變成如下所示:
采用了依賴注入方式后,其實可以發現事實上已經沒有分層概念了。無論高層還是底層,實際只依賴于抽象,整個分層好像被推平了,這就引入下一個架構六邊形架構。
六邊形架構(Hexagonal architecture)
六邊形架構是Alistair Cockburn在2005年提出,解決了傳統的分層架構所帶來的問題,實際上它也是一種分層架構,只不過不是上下或左右,而是變成了內部和外部。在《實現領域驅動設計》一書中,作者將六邊形架構應用到領域驅動設計的實現,六邊形的內部代表了application和domain層。外部代表應用的驅動邏輯、基礎設施或其他應用。內部通過端口和外部系統通信,端口代表了一定協議,以API呈現。
按照領域分層的模型,在應用層和領域層內置后,一個典型的六邊形架構應用有兩個端口,一個端口對應用戶接口層,用于應用控制,一個對應數據訪問層,用于數據獲取和持久化。每個端口都可以對應幾個適配器,該應用可以被自動化測試,系統層面的回歸測試,用戶交互操作,遠程HTTP調用,REST調用或者其他。在數據方面,通過配置使用外部的數據庫,可以是Oracle數據庫,mock的數據庫,測試數據庫或生產數據庫,從而實現應用和外部數據庫的解耦。
另外值得一提的是,在六邊形架構中,自動化測試和用戶具有同等的地位,在實現用戶界面的同時就需要考慮自動化測試。它們對應相同的端口。六邊形架構不僅讓自動化測試這件事情成為設計第一要素,同時自動化測試也保證應用邏輯不會泄露到用戶界面,在技術上保證了層次的分界。
使用六邊形架構的時候,我們應該根據用例來設計應用程序,而不是需要支持的客戶數量來設計。任何客戶都可能向不同的端口發出請求,但是所有的適配器都使用相同的API。
六邊形架構的功能非常強大,可以作為基層架構并用于支持系統的其他架構。
面向服務架構
SOA
略
REST
REST表述性狀態傳遞( Representational State Transfer),是 Fielding博士在2000年他的博士論文中提出來的一種軟件架構風格。
REST全稱是 Resource Representational State Transfer,通俗來講就是:資源在網絡中以某種表現形式進行狀態轉移。分解開來:
- Resource:資源,即數據(前面說過網絡的核心)。比如 newsfeed,friends等;
- Representational:某種表現形式,比如用JSON,XML,JPEG等;
- State Transfer:狀態變化。通過HTTP動詞實現。
作為一種被廣泛使用,甚至被濫用的架構流行語,很多人了解REST是把它作為一種Web通信協議,但實際REST是這是一種架構風格。架構風格之于架構就像設計模式之于設計一樣。它將不同架構實現所共有的東西抽象出來,使得我們在談及到架構時不至于陷人技術細節中。
作為一種架構風格,就需要遵循其提出的一系列架構級約束。REST的約束有:
- 客戶-服務器(Client-Server),通信只能由客戶端單方面發起,表現為請求-響應的形式。
- 無狀態(Stateless) 通信的會話狀態(Session State)應該全部由客戶端負責維護。
- 緩存(Cache) 響應內容可以在通信鏈的某處被緩存,以改善網絡效率。
- 統一接口(Uniform Interface) 通信鏈的組件之間通過統一的接口相互通信,以提高交互的可見性。
- 分層系統(Layered System)通過限制組件的行為(即,每個組件只能“看到”與其交互的緊鄰層),將架構分解為若干等級的層。
- 按需代碼(Code-On-Demand,可選)
如果一個系統滿足了上面所列出的約束,那么該系統就被稱為是RESTful的。當然一旦遵循了以上約束,也意味著這個系統就會享受到這種架構風格帶來的好處。實際上HTTP1.1也正是基于REST架構風格來設計的,REST架構也是Web之所以取得成功的技術架構方面因素的總結。
符合REST原則的系統將具有更好的松耦合性。通常來講,添加新資源并在已有資源中創建到新資源的鏈接是非常簡單的。另外基于 REST的系統也是非常容易理解的,因為此時系統被分為很多較小的資源塊,每一個資源塊都可以獨立地測試和調試,并且每一個資源塊都表示了一個可重用的人口點。 HTTP設計本身以及URI成熟的重寫與緩存機制使得 RESTful HTTP成為一種不錯的架構選擇,該架構具有很好的松耦合性和可伸縮性。
REST和DDD
領域模型通過REST直接暴露給外界并不被推薦,要將 DDD與 RESTful HTTP合并起來使用,推薦的方式是:為系統接口層單獨創建一個限界上下文,再在此上下文中通過適當的策略來訪問實際的核心模型。它將系統接口看作一個整體,通過資源抽象將系統功能暴露給外界,而不是通過服務或者遠程接口。
另一種方法用于需要使用標準媒體類型的時候。如果某種媒體類型并不用于支持單個系統接口,而是用于一組相似的客戶端.服務器交互場景,此時我們可以創建一個領域模型來處理每一種媒體類型。這樣的領域模型甚至可以在服務器和客戶端之間進行重用。
這也是一種由外向里的、橫切式的方法。在上面提到的工作組例子中,有多種常用的格式可以用于領域模型。比如 iCal格式(一種標準的互聯網日歷格式)。我們首先選擇一種媒體類型,即 iCal,然后再根據這種格式創建領域模型。該模型可以用于任何能夠理解 ical格式的系統,比如服務器程序,或者 Android客戶端。在采用這種方法時,服務器需要處理多種類型的媒體類型,而同一種媒體類型又可以用于多個服務器。
如何在以上兩種方法之間進行選取呢?這在很大程度上取決于系統設計者對可重用性上的要求。第一種方法比較適合更加專屬的系統,而第二種方法更適合那些通用的系統。
兩個建議:
- 設計好REST的API接口后,通過Application層去順序調用合適的領域對象,這也正是Application層的價值所在。
- 不要盲目的遵循教條的理論。我們應該盡可能遵循各種理論和最佳實踐,但是如果不行的時候就先把這些放到一邊去(當然這需要在慎重考慮后)。
參考資料: