“純天然”的微服務架構
從分層架構到六邊形架構
在計算機領域很多概念都是抽象的,為了能夠清晰地劃分這些抽象的複雜概念,隨處可見各種分層式的設計方式。比如計算網路的七層通訊協議,把複雜的網路通訊劃分成更容易管理的層,明確定義各層的職責範圍,各層都可以採用最合適的技術來實現。當任何一層發生變化時,只要層間介面關係保持不變,則在這層以上或以下各層均不受影響。
在常見的軟體系統的架構中也有一種4層架構設計,通常從前到後會劃成:使用者介面層,應用層,領域層,基礎設施層。

-w300
- 使用者介面層只需要關注如何同使用者打交道,不需要關注核心的業務邏輯,例如把使用者提交的資料經過轉碼處理後提供給應用層。
- 應用層的關注點是如何組裝核心的領域層來提供複雜的組合服務,例如應用層可以從領域層的使用者倉庫獲取使用者資訊,從領域層的訂單倉庫獲取訂單資訊,然後在應用層把使用者資訊和訂單資訊組裝在一起返回給使用者介面層
- 領域層處理的業務問題,是更加內聚的領域業務,不同業務之間要做到低耦合。例如在使用者聚合中就只處理使用者相關的邏輯,不應該包含訂單,支付這些其它的業務邏輯。
- 基礎設施層是不包含任何業務邏輯的底層支援,例如生成pdf或者excel,或者把資料持久化到資料庫的ORM框架
但是這樣的分層架構會導致其他三層尤其是領域層對基礎設施層產生依賴。這種依賴會導致當基礎設施層發生變更的時候,會導致其它三層也要跟隨變動,這是我們不希望放生的事情,尤其是涉及核心業務邏輯的領域層,我們不希望因為某種生成excel或者pdf的技術發生變化而要修改領域層。
那麼如果考慮對基礎設施進行解耦的方法,可供選擇的的方式有 依賴倒置 ,在每一層定義對應的技術介面,然後在基礎設施層實現這個介面,再通過一些依賴注入框架在執行式把實現注入進來,這樣就把原來其它三層依賴基礎設施層的情況倒置成了基礎設施層依賴其它三層了。

-w300
有趣的是,當我們在分層架構中採用依賴導致原則時,我們會發現事實上已經不存在分層的概念了。無論高層還是低層,都只依賴於抽象的介面。如果我們這時把分層架構推平,這就是在微服務領域非常著名的 六邊形架構 。
六邊形架構是Alistair Cockburn提出的,其實是把原來的分層架構轉換了一下視角,從原來的上下結構變成了內外結構。六邊形架構把原來一個系統的輸入和輸出都統一來處理,輸入輸出對接的都是外部系統,都需要一個介面卡來進行轉換。所以不論是處理輸入的HTTP請求,還是處理輸出的資料庫儲存都應該有一個介面卡來負責接入。介面卡會呼叫負責業務邏輯組合的應用層,而應用層負責呼叫核心的領域層。
相比於應用層,領域層通常更少變化。在這樣的架構關係下,當需要修改一個負責連線資料庫的ORM工具時,或者是應用層返回的訂單資訊需要增加物流狀態時,都不需要去修改最核心的領域層。

圖片來自:slideshare.net-w500
為什麼是六邊形
不知道大家在看到六邊形架構這個名字的時候會不會產生一個疑問,就說是為什麼是六邊形,不是七邊形,五邊形。
這個問題其實困擾了我蠻久,但是當我在鄭也夫老師的社會學專題50講裡面找到了答案。六邊形這個概念源自於城市規劃,有兩個德國人,一個是克里斯塔勒,他的研究興趣是人口密度和商品銷售關係。他1933年寫出一部書,叫做《德國南部中心地原理》。另外一個是廖什 ,他的興趣在於研究空間秩序,他在1940年寫出一部書叫做《經濟空間秩序》。
這兩個人各自獨立的提出了六邊形理論:
每個商家銷售的範圍是一個圓。因為人們買東西有交通成本,所以導致著以一個賣點為中心,銷售範圍是圍繞著這個中心畫的一個圓。當人口越來越多的時候,商家也就越來越多,那個圓形越來越多,導致著一個圓形跟另一個圓形相互擠壓,最後壓成了一個蜂巢一樣的緊密相連的正六邊形的組合體。

-w400
最低階的銷售點是集市,銷售一些最通常的日用品,構成了最小的六邊形拼湊出來的平面。稍大一點的城市會賣衣服、鞋等等中檔的東西,它們就是中型的六邊形。大的都市會賣一些很高檔的東西,珠寶、珍貴的皮毛等等。這些大的正六邊形拼湊出一個大的平面。並且在一個廣大的地域中,大都市銷售區是大六邊形的對接,其中包含著中小六邊形。
除了人類的集市,還有一種自然界的天然六邊形就是蜂巢。但是你有想過這個問題嗎,蜜蜂在搭建蜂巢的時候又沒有測量工具,它們是怎麼搭建出來比例那麼標準的的六邊形蜂巢的呢。
其實蜜蜂根本就不曾想過要搭建六邊形,蜜蜂在搭建蜂巢的時候是按照記得身體的大小圍出了圓形的一個個蜂巢,請看下圖:

-w400
然後這一個個小圓經過蜂巢自己本身的拉伸和風化,就變成了六邊形。蜜蜂本來就是要造圓形巢的,但是由於自然界本身的趨於穩定的焓變規則,軟的圓形巢就拉成六邊形了。
[圖片上傳失敗...(image-750f2e-1541523534115)]
那麼為什麼圓形會被拉伸成六邊形呢?阿爾法小分隊有一個視訊專門介紹了這個問題的數學原理( ofollow,noindex">視訊傳送門 )。在視訊中用小時候經常玩的泡泡做了一個簡單的實驗,當四個泡泡湊仔一起的的時候,在泡泡邊界上出現的所有夾角都是120度。所以當很多個泡泡擠在一起的時候,由於在拉扯力的作用下邊界夾角都是120度,所以一個個泡泡就“天然的”變成了六邊形。而蜂巢,人類集市是遵循這個自然規律。

-w400
回到微服務架構,雖然“六邊形架構”這個概念是Alistair Cockburn創造出來的,但是由於六邊形代表了自然規律,所以我認為六邊形架構是“純天然”的。而且如果我們把建立的微服務看成一個個小泡泡,那麼把這些小泡泡排列在一起以後,自然法則會讓他們變成一個個六邊形。而不可能是五變形或者七邊形。
介紹完了六邊形的故事,讓我們進入六邊形架構的微服務內部,看看裡面的程式碼是什麼樣子的。
一個六邊形架構的程式碼結構示例
首先在一個微服務的根目錄上應該只有3個包,adapter,application和domain。而且還要嚴格限制依賴關係:
- domain中不可以依賴任何application和adapter中的程式碼
- application中不可以依賴任何adapter中的程式碼

-w400
假設一個簡單的場景:使用者從介面提交一個支付請求,最終這個支付請求的相關資訊會被存入資料庫中。
接受請求這段很好實現,在adapter層裡有一個controller來處理請求,把請轉換成領域模型payment,然後呼叫application層SubmitPaymentApplication的submit方法來提交payment。然後SubmitPaymentApplication會呼叫domain層的PaymentRepository. 這時候會有一個麻煩的問題,通常來說Repository裡面都會依賴具體的ORM工具,比如hibernate或者Mybatis。但是前面說過我們希望Domain層是不要有任何外部依賴的,所以解決這個問題的方法就是在只在domain層定義PaymentRepository的介面,然後在Adapter層使用ORM工具實現這個介面。

-w400
ORM工具是在面向物件程式中進行資料庫持久化的好用工具,而且現在流行的ORM工具都提供了從資料庫表到實體類的自動生成功能,讓開發者儘可能的減少機械化的工作。但是這些好用的工具帶來的一個問題就是實體越來越貧血,實體裡面只是有一堆屬性和對應的get/set方法而已。但是在六邊形架構裡面,由於domain層的實體不需要由ORM生成,資料庫持久化相關的工作都在外面的adapter層中完成,所以開發者就可以放心的在domain中使用列舉等各種程式語言的特性,還可以在實體中放心的實現各種本來就屬於實體的行為。
思辨小結
這幾年由於微服務的原因六邊形架構也變得熱了起來。但是要真的用好六邊形架構,我們需要對這個概念有一個更深入的理解:知道它是怎麼提出來的?它解決的是什麼問題?為什麼是六條邊?弄清楚了這些,在應用六邊形架構的時候才能清楚地知道什麼邏輯只能屬於哪一層,哪一層不能依賴哪一層。 堅持這些原則的價值是:當有新需求或者需求發生變更時,重用程式碼和修改程式碼都是讓人愉悅的事情。
把使用ORM的資料庫持久化實現放在adapter的一個可能問題是,由於domain層的實體更貼近業務,所以這些實體模型無法直接給ORM使用,這就需要在adapter裡面再實現一套專門對應資料庫表的物件模型出來,並在adapter裡面吧domain的實體轉換成這些模型再進行資料庫操作。這樣看起來好像會有兩套冗餘的業務模型,一個在domain中,一個在adapter裡面。但其實只有domain中的才是真正的業務模型,adapter裡面的模型只是資料庫表的一個對映物件而已。 這種方式很好地把領域模型和持久化相關實現進行了解耦,我們可以完全拋開資料庫或者ORM的技術限制,去設計能夠真正和業務對應的領域模型。