SOA、REST 和六邊形架構
上一篇:《IDDD 實現領域驅動設計-架構之經典分層》
閱讀目錄:
DDD 的一大好處就是並不需要使用特定的架構,經典分層架構只是一種,由於核心域位於限界上下文中,我們可以使用多種風格的架構,既然如此,我們應該把眼界看的更寬廣些,有意思的東西多着呢。
SOA 和 REST 這兩個貨,我們都比較熟悉,他倆並不是由 DDD 引入,但卻可以適用於 DDD。我個人覺得,要想把他倆發揮好,最好結合六邊形架構(也可以稱之為端口和適配器),至於原因,請接着往下看。
很重要的一張圖,摘自:《實現領域驅動設計 P111》
1. SOA-面向服務架構
SOA(Service-Oriented Architecture),我沒用過這貨,下面說一下自己對於它的認識,可能不是很准確。
Service 意為服務,面向服務,也就是在 SOA 架構中,服務是核心。業務系統中分布着大量的業務模塊,這些業務模塊進一步提煉就是服務,然后通過規定的服務契約發布出來,這些服務集合起來就是 SOA 的核心,服務調用者可以是多樣的,Web 端、移動端、桌面端都可以,也就是說它是分布式的架構,這些服務調用者調用的時候,要遵守發布者規定的一些契約和協議,而在服務本身或者之間也需要一定的契約和協議用來約束,要不然整個服務集合就會亂套,比如服務發布協議要有統一的規范,不能說一個服務是一個規范,服務之間也需要進行抽象抽離,盡可能的做到重用性等等。
因為我沒用過 SOA,所以有些東西表達不出來,我用另一種概念去試着理解它,那就是 OWIN,他們可能不是一個領域的概念,但我覺得有些東西都是共通的,我們都知道 OWIN 體系結構中,有四大模塊:Host、Server、Middleware 和 Application,他們之間最重要的一點就是組件化,並且任何一種模塊都不依賴於彼此的任何一方,這就是自治,一種模塊也構建不成 OWIN 的概念,這就是模塊組合,一種模塊的任意實現,都可以組合成整個 OWIN,原因是什么?因為他們彼此都遵守 OWIN 請求處理管道的協議。
回到 SOA 上面,你會發現,他們的概念其實是共通的,夏天到了,出去游玩,走着走着口渴了,我們就在路邊買一個巨大的椰子,然后蜂擁而至一大堆基友,他們人手拿着一個吸管,而且種類和顏色各不相同,然后你的椰子上插滿了各種習慣,看起來就像一個刺蝟一樣,你最后的下場可能就只有用刀划開個口子喝了,不管是用吸管,還是用到划開個口子,我們最后的目的都是喝椰子汁,只是使用的工具不同,一個椰子可能里面的汁更好喝,外面的更難喝。在這個日常生活示例中,一個椰子可以看作是一個 SOA 架構,內部就是各種小的服務(可以看作是一塊一塊的椰子肉,里面的更好吃),各種各樣的吸管和刀子可以看作是服務調用者,而椰子皮也可以看作是服務協議,因為椰子汁需要椰子皮的包裹才不會灑出來。
SOA 架構原則:
- 服務封裝
- 服務松耦合(Loosely coupled)- 服務之間的關系最小化,只是互相知道。
- 服務契約 - 服務按照服務描述文檔所定義的服務契約行事。
- 服務抽象 - 除了服務契約中所描述的內容,服務將對外部隱藏邏輯。
- 服務的重用性 - 將邏輯分布在不同的服務中,以提高服務的重用性。
- 服務的可組合性 - 一組服務可以協調工作並組合起來形成一個組合服務。
- 服務自治 – 服務對所封裝的邏輯具有控制權
- 服務無狀態 – 服務將一個活動所需保存的資訊最小化。
- 服務的可被發現性 – 服務需要對外部提供描述資訊,這樣可以通過現有的發現機制發現並訪問這些服務。
SOA 相關資料:
2. REST 與 RESTful
RESTful 架構概念,是 Fielding 提出的,Fielding 這號人物就是 HTTP 協議的主要設計者之一。我們先看下 RESTful 這個詞,ful 是跟在名詞之后,表示程度,什么什么的,例如 helpful 樂於助人的,因此我們可以看出符合 REST 的架構就可以稱為 RESTful,接着我們看下 REST,全稱為“Representational State Transfer”,意為“表現層狀態轉化”。
在符合架構原理的前提下,理解和評估以網絡為基礎的應用軟件的架構設計,得到一個功能強、性能好、適宜通信的架構。 -Fielding
這是 Fielding 在論文中所提到的,對於 REST 雖說是架構,但如果深入一點,就像是 HTTP 協議一樣,可以看成一種規則或是協議。我們從一個地點到另一個地點,可以坐汽車、高鐵、飛機等,對於 REST 就像是其中的一種交通方式,但 REST 的根本是 HTTP 協議,也就是說 REST 是基於 HTTP 協議的,這點就像坐汽車必須要有公路,坐高鐵必須要有鐵路是一樣的道理,有時候為什么選用 REST,就像我們從南京到徐州,選擇坐高鐵而不選擇坐飛機一樣。
“Representational State Transfer”我們分解下:
- Representational 表現層:表現層表現什么,應該呈現資源(Resources),一個圖片、一段文字、一個文件都成為資源,每個資源都用一個 URI(統一資源定位符)指向它,表現層就是調用 URI 把資源呈現出來,而且只是呈現,不做其他操作。舉個例子:有些網址最后的".html"后綴名是不必要的,因為這個后綴名表示格式,屬於"表現層"范疇,而 URI 應該只代表"資源"的位置。它的具體表現形式,應該在 HTTP 請求的頭信息中用 Accept 和 Content-Type 字段指定,這兩個字段才是對"表現層"的描述。
- State Transfer 狀態轉化:訪問一個網站,就表示客戶端和服務器發生一次交互行為,在這個過程中,就不發生數據和狀態的轉化,上面說到 HTTP 協議具有無狀態性,如果客戶端操作服務器,必須要狀態轉化,這個體現在表現層上,所以叫“表現層狀態轉化”。
通過上面的理解,可以總結下什么是 RESTful 架構:
- 每一個 URI 代表一種資源。
- 客戶端和服務器之間,傳遞這種資源的某種表現層。
- 客戶端通過四個 HTTP 動詞(PUT、GET、POST 和 DELETE),對服務器端資源進行操作,實現"表現層狀態轉化"。
上面 REST 和 RESTful 的概念,摘自很久之前的一篇博文:初試ASP.NET Web API/MVC API(附Demo),並做了部分修改。
我再來說一下自己現在的理解,首先,REST 是一種架構風格,而不是一種架構,一種架構風格可以用多種架構進行實現,一個架構中也可能包含多種架構風格,這兩者的關系,你可以理解為抽象和實現的區別,另外,REST 嚴格來說,應該屬於 Web 架構的一種架構風格,因為它離不開 HTTP 協議。
REST 架構風格的兩個關鍵:
2.1 資源(Resources)
Web 資源的表述是 URI,一個規范的 URI 就是開放出來的一個資源,它是唯一並具有一定的規范,對資源的操作方式就是 HTTP 提供的方法(PUT、GET、POST 和 DELETE),資源的表現形式是多樣的,比如:JSON、XML、YAML 等。
我們看一下常用的 URI:
- GET: cnblogs.com/getUser/1
- POST: cnblogs.com/createUser
- PUT: cnblogs.com/updateUser/1
- DELETE: cnblogs.com/deleteUser/1
很顯然,這種 URI 不符合 REST 對資源的定義,我們嘗試修改一下:
- GET: cnblogs.com/user/1
- POST: cnblogs.com/user
- PUT: cnblogs.com/user/1
- DELETE: cnblogs.com/user/1
cnblogs.com/user/1
,這個 URI 一般表述的含義是:Id 為 1 的 User 資源,這個僅僅是表述,URI 並不包含對這個資源的任何操作,所以,像 getUser、createUser 這類操作就不合適,資源的操作是通過 HTTP 提供的方法,還有一點是,比如 POST 中的cnblogs.com/user
和 cnblogs.com/user/1
有什么不同?第一種 POST,一般是創建一個新的 User 資源,創建完成后,一般會返回這樣的一個 URI:cnblogs.com/user/1
,第二種 POST,不是說創建一個 Id 為 1 的 User 資源,而是在 Id 為 1 的 User 資源下創建某種資源,你會發現,好的 URI 設計應該不包含動詞。
2.2 狀態(State)
首先,REST 是無狀態的(Statelessness),我之前是一直不理解狀態的含義,好像還把狀態和資源格式(XML、JSON)混為一談,現在想想確實太荒謬了,關於狀態的幾個要點:
- 狀態分為:應用狀態(Application State)和資源狀態(Resource State)。
- 應用狀態:與某一特定請求相關的狀態信息。
- 資源狀態:反映了某一存儲在服務器端資源在某一時刻的特定狀態。
- 客戶端負責維護應用狀態,而服務端維護資源狀態。
- 服務器端不保有任何與特定 HTTP 請求相關的資源。
REST 中的無狀態其實指的是應用狀態,無狀態的表現是服務端不保存應用狀態,也就是客戶端發起與請求相關的狀態,應用狀態是客戶端在發起請求時提供的,那狀態轉化是什么意思?其實指的是服務端資源狀態的轉化,表現在客戶端中,也就是上面所說的“表現狀態轉化”。
在 ASP.NET 應用程序中,我們都知道 Session 的概念,意為會話,也就是有關用戶請求的會話,應該划分為應用狀態,這個會話狀態是保存在服務端的,從這一點上來說,這種設計就是 unRESTful 風格,REST 中的無狀態,是客戶端和服務端交互中所表達的一種概念,有時候,雖然應用狀態可能不保存在服務端,但客戶端發起的某些請求所表達的含義不恰當,也可以認為是不符合 RESTful 風格,比如客戶端發起的請求中包含 Session ID,在服務端看來,客戶端發起的這個請求,所表達的含義是要獲取某個 Session,具體來說就是會話狀態保存在服務端,這個雖然只是一個客戶端請求的概念,但也可以認為這種設計是 unRESTful 風格。
總的來說,架構風格不是某一種具體架構,它是一種風格。
REST 參考資料:
3. 六邊形架構
上面有關 SOA、REST 的講述,絲毫沒有 DDD 的半點影子,它們並不是為 DDD 而生,在架構設計的時候,你也可以單獨使用它們,但對於整體 DDD 架構設計來說,總覺得會有些不對勁,這時候,就需要了解下六邊形架構。
六邊形架構(Hexagonal Architecture),又稱為端口和適配器架構風格,其中的“六”具體數字沒有特殊的含義,僅僅表示一個“量級”的意思,六邊形的定義只是方便更加形象的理解。
我們知道分層架構的重要作用就是避免耦合的出現,經典分層架構和六邊形架構都是分層架構的一種,但是所發揮的作用會有些不同,經典分層架構更多的精力放在抽象的分離上,每個層的職責分的很明確,各個層的依賴關系更加抽象化,從而避免耦合的出現,而在六邊形架構中,是用“組件化”的形式來避免耦合的出現,每個業務單元盡可能的最小化,然后把這些業務組件集合起來,用一個錘子把他們都拍扁,所以,在整個集合中,這些小的業務單元都是“平等的”,這種方式用一個詞來概括,那就是“扁平化”。
在博文一開始的時候,說過這樣一句話:由於核心域位於限界上下文中,我們可以使用多種風格的架構。
那為什么核心域位於限界上下文中,DDD 就可以使用多種風格的架構?我們來分析一下,核心域指的是業務系統中的核心業務邏輯,這個通常用通用語言表述,限界上下文是一種邊界,它包裹的是核心域,也就是說,核心域並不是組件化的形式表現,你可以把它看作是一種聚合概念,用限界上下文來進行限定,這個概念在之前的博文中有說明。對於業務系統來說,核心域毫無疑問是核心概念,DDD 的專注點也就是它,開發人員和領域專家會花大量的時間去探討它,但對於架構設計來說,核心域只是業務上的專注點,並非是架構設計上的核心點,所以,也可以這樣說,DDD 和架構設計,其實嚴格來說應該是兩個領域方面的概念,他們的結合才真正構成整體的業務系統,換句話說,最爛的架構設計配上最好的 DDD(業務上的),這也是可以的,因為 DDD 專注的是業務實現,而並非是技術實現。
我們知道,經典分層架構分為四層,而對於六邊形架構,一般會分成三層:
- 領域層(Domain Layer):最里面,純粹的核心業務邏輯,一般不包含任何技術實現或引用。
- 端口層(Ports Layer):領域層之外,負責接收與用例相關的所有請求,這些請求負責在領域層中協調工作。端口層在端口內部作為領域層的邊界,在端口外部則扮演了外部實體的角色。
- 適配器層(Adapters Layer):端口層之外,負責以某種格式接收輸入、及產生輸出。比如,對於 HTTP 用戶請求,適配器會將轉換為對領域層的調用,並將領域層傳回的響應進行封送,通過 HTTP 傳回調用客戶端。在適配器層不存在領域邏輯,它的唯一職責就是在外部世界與領域層之間進行技術性的轉換。適配器能夠與端口的某個協議相關聯並使用該端口,多個適配器可以使用同一個端口,在切換到某種新的用戶界面時,可以讓新界面與老界面同時使用相同的端口。
在六邊形架構中,領域層和技術沒半毛錢關系,可以看作是業務的技術實現,端口層包裹在領域層在外,外部要向和領域層“交流”,則必須通過端口層的“首肯”,反過來,領域層向外面“交流”也是一樣,但這種方式一般是技術上的,比如領域對象的管理:
領域層想要獲取某一個領域對象,來進行業務操作實現,然后告訴端口層說:“端口小弟,哥需要一個 XXX Domain Model,立馬去叫人搞!”,端口小弟心想,老大發話了,得趕緊的啊,然后就在自己胸前,貼出了這樣一段告示:能逮到 XXX Domain Model 的適配器殺手們,請速速到俺這里,必有重賞!告示一貼出,適配器殺手們蜂擁而至,然后根據自己的能力來進行判斷,畢竟 XXX Domain Model 也不是那么容易擒服,也不是隨便一個適配器殺手就能搞定的,這需要一定的能力,最后能做的適配器殺手進行揭此告示。
以上是即興想到的一個情節,其實就是六邊形架構,從內到外的一個過程體現,反過來,從適配器層到領域層也一樣,這種方式一般是接受用戶請求處理開始。其實,從某種意義上來說,端口層有點像應用層,只不過是拍扁之后的應用層,而適配器層有點像基礎設施層,只不過是全能型的基礎設施層,六邊形是環形結構,所以表述起來更加形象。
還有一點是,端口層的存在,還有利於測試的進行,這些測試不是在領域層進行的,所以它絲毫不會影響領域層的進度,對於端口層的測試,一般是測試領域層中業務的正確性。
以上是六邊形架構的一些概念,那再結合 SOA 和 REST,該如何實現呢?因為在六邊形架構中,適配器層是以組件性質的方式提供服務,他和領域層進行聯系要通過端口層,這個端口層可以看作是服務的一種協議或規范,這就是 SOA 和 REST 的用武之地,來扮演適配器層和端口層的角色。還有一個概念不同點是,服務交互分為服務端和客戶端,對於 SOA 和 REST 本身來說,他們的實現就是服務端,但在六邊形架構中,他們更像是一個客戶端,並且表現形式是組件化的服務,而領域層是服務端,通過端口協議來調用組件服務提供一些操作實現。
SOA、REST 和六邊形架構的結合,可以和一開始的那張圖進行對比:
說明:本圖摘自:《實現領域驅動設計 P115》
參考資料:
距離上一篇有很長的一段時間了,經典分層架構我是用過的,所以會有很多的感觸,也有很多的文字需要表達,但對於 SOA、REST 和六邊形架構,我並沒有真正實質性的用過,所以,對於一個你不曾接觸的概念,要把一些東西寫出來是很難的,寫不出來,那就看書、看文章、找資料,把有些有感觸的文字記錄下來,然后通過自己的理解再表達出來,這種方式過程雖然很慢,但還是有一定的效果。
關於 DDD 架構設計,還有很多很多的內容,比如 CQRS、事件驅動架構、網格分布式計算等等,這個需要時間來消化。
因為沒有實踐過,所以難免會有一些問題,還請大家斧正!!!