追溯微服務架構的淵源,一般會涉及到六邊形架構。追溯六邊形架構的起源,要看始作俑者Alistair Cockburn的這篇文章 http://alistair.cockburn.us/Hexagonal+architecture, 讀原文,譯重點,記感受, 如下:
六邊形架構的意圖
采用同等的方式,應用可以通過用戶,程序,自動化測試或批處理腳本來驅動,而獨立于最終的運行環境及數據庫進行開發和測試。
當外部事件到達端口的時候,相應的適配器將其轉化成合適的過程調用或者消息,然后傳遞給應用,而應用對輸入設備一無所知。輸出內容時,應用通過端口把要傳遞出去的消息傳給適配器,適配器再針對信息接收者的具體實現要求將其轉化成合適的信號。從語義上來說,應用跟它周圍的適配器有著良好的互動,而對適配器外部的一切無感知。
這是一種設計模式,被Cockburn定義為“端口和適配器模式“,設計模式不僅指導了代碼的實現,同時支持結構的實現,又是一種解耦合的技巧。
所需解決的問題場景
不能解決問題的架構,都只是架構師手中的玩具。六邊形架構面對的一個典型問題是業務邏輯與用戶界面的代碼交叉,這是開發中一個非常令人頭痛的問題,有三個惡果:
- 系統無法方便地進行自動化測試,因為部分邏輯依賴界面元素的,比如輸入框的長度或按鈕的位置,而這些細節又是易變的;
- 同樣,無法把一個面向人機交互的系統移植到一個自動化處理的系統
- 另外,應用程序之間的相互驅動變得很困難,有時甚至不可能的
“萬能的那一層”出現了,可以在架構里增加一個新的層,并承諾不會有業務邏輯被放到著一新層里。然而隨著時間的推移,會發現新的層里還是摻雜了業務邏輯,于是老問題又出現了。
怎么辦呢?假設一下,如果應用提供的每一個功能都有相應的API會是怎樣的結果呢?QA可以通過自動化測試腳本來監測新改的代碼,檢驗是否會破壞已有的功能。業務專家在GUI出來之前就可以創建自動化測試用例,作為程序員們檢測是否正確完成工作的依據,同時,這也將成為測試部門所運行的測試的一部分。應用以"headless"模式部署,其它程序通過調用API的方式使用所提供的功能。這種方式使得復雜系統的設計變得容易,面向業務的應用之間不需要人工干預就可以互相調用。最后,回歸測試檢測到出現問題的地方,并加以修復,而保證業務邏輯不會進入表現層。
另一個典型的問題是,如果應用綁定了外部數據庫或其它服務,當數據庫宕機或者正在遷移的時候,依賴數據庫的程序就無法正常工作。這會導致響應延遲,這是一種相當糟糕的體驗。
這兩個問題之間沒有明顯的聯系,但它們之間看起來是對稱的。這又是一種面向接口的設計么? 可以這樣理解,因為接口的概念外延太大了,而在具體的編程語言實現中,interface 有往往太小了。這里把它明確為端口和適配器。
方案
不論用戶端交互問題還是服務端數據庫編程問題,其同源原因就是在設計和實現過程中出現的業務邏輯混淆,以及與外部實體之間的交互關系。我們要關注的非對稱性不是應用的"左邊"和"右邊",而是它的"內部"和"外部",該屬于"內部"的代碼就不要泄露到"外部"去,其基礎理論還是那些基本的設計原則。
六邊形架構的核心理念是:應用通過"端口"跟外部進行交互的。"端口"讓人聯想到操作系統的端口,任何符合協議的設備都可以被插到相應的端口上。端口的協議是為了兩個設備之間能夠進行通信而設計的,位于OSI 7層協議模型中的傳輸層。
對應用來說,API就是協議。對于每一個外部設備,都有對應的適配器把API轉換成自己所需要的信號,反之亦然。用戶圖形界面就是一個很好的例子,它是把用戶操作映射到端口API的適配器,還有其它的例子如自動測試套件,批處理驅動器,以及任何需要跨應用程序交互的代碼。
對于應用的數據處理層面,應用通過與外部實體交互得到數據,這里用到的協議一般指數據庫協議。從應用角度來看,把SQL數據庫遷移到普通的文件或者其它類型的數據庫,API仍然保持不變。適應于同一個端口的適配器還包括SQL適配器,文件適配器,以及更重要的數據庫mock,它可以是駐存在內存里的數據庫,不一定是真實的數據庫。
很多應用都只有兩個端口:用戶端端口 和 數據庫端端口。這種情況看起來是對稱的,很自然地就會用單維度多層次的架構來構建它。在開發應用程序時,就是我們經常看到的三層、四層或五層架構。這種架構有兩個問題:
- 很容易跨越層間的邊界,把業務邏輯滲透到其它層中去。
- 有的應用可能不止需要兩個端口,所以不能用單維度架構來構建。
這就是六邊形架構提出的原因,它著重解決對稱性問題,應用通過端口與外部進行交互,而外部的實體也可以用同樣的方式來處理。六邊形架構強調以下兩點:
首先,通過"內外"的不對稱性以及端口的特點,擺脫單維度多層次架構的束縛。可以定義不同數量的端口,2個,3個或者4個,這里說的六邊形不限于只有六個邊, 可以根據需要加入更多的端口和適配器,"六邊形架構"只是視覺上的一種叫法。
其次,關注整體架構的結果導向,一個端口對應一個或一組有目的交互行為。但一個端口一般會有多個適配器,可以是無人應答機,語音留言機,按鍵電話,用戶圖形界面,測試套件,批處理驅動器,HTTP接口,程序之間的接口,mock的數據庫,或者真實的數據庫。
從應用層面來看,這一架構的目的是將注意力聚焦在內外非對稱性上,讓外部的實體從應用視角來看都是一樣的。
結構
一個典型的六邊形架構應用有兩個端口,每個端口對應幾個適配器,這兩個端口分別用于應用控制和數據獲取。該應用可以被自動化測試,系統層面的回歸測試,用戶交互操作,遠程HTTP調用或者另外一個本地應用驅動。在數據方面,通過配置使用外部的數據庫,可以是Oracle數據庫,mock的數據庫,測試數據庫或生產數據庫,從而實現應用和外部數據庫的解耦。應用的功能說明是依據六邊形內部的接口來編寫的,而不是依據外部可能用到的任何一種技術。
對于一個典型的三層架構而言,簡化起見,每個端口只給出兩個適配器。架構的頂層跟底層可以適用多個適配器,這些適配器是可以按照一定順序來開發。帶有數字的箭頭展示了一個團隊是如何按照一定順序來開發和使用應用的:
- 用測試套件(如FIT)來驅動應用,用mock的內存數據庫模擬真實數據庫。
- 給應用增加GUI,但仍然使用mock數據庫。
- 在集成測試的時候使用自動化測試腳本(例如,通過Cruise Control觸發),數據庫換成包含測試數據的真實數據庫。
- 用戶在生產環境使用應用,數據庫也是真實的。
代碼示例
FIT的文檔給出了一個簡單的例子來說明端口與適配器模式,它是一個計算折扣價格的應用:
discount(amount) = amount * rate(amount);
在這個應用里,amount來自用戶的輸入,而rate來自數據庫,所以需要兩個端口。先用測試代碼跟rate常量來測試,然后再使用GUI跟mock數據庫,來自IHC的Gyan Sharma提供這個示例的代碼,具體參考原文。另一個用Ruby和Rack實現的例子可以看這里https://github.com/totheralistair/SmallerWebHexagon。
六邊形架構中的左右非對稱性
六邊形架構強調端口之間的相似性。在實現的時候一般有兩種風格,稱之為"主"和"從",或者叫驅動者跟被驅動者,實際上是CS結構的又一體現。
在上面的例子中,FIT被用在左邊的端口上,而mock的東西在右邊。在三層架構中,FIT在最頂層,mock在最底層。這兩者之間的區別在于是誰觸發了會話,或者誰在會話中起主導作用。FIT就是"主",這個框架就是被設計用來通過腳本來驅動應用的。Mock數據庫就是"從",數據庫被設計用來響應來自應用的查詢或記錄變更事件的。
根據系統用例,把"主"的端口和適配器放在了六邊形的左邊,而"從"的端口和適配器放在了六邊形的右邊。它們之間的關系以及它們的實現方式是很有用的,但前提是要用在六邊形架構中。端口與適配器模式最大的好處就是可以讓應用可以完全獨立地運行。
六邊形架構的應用邊界
六邊形架構對用例編寫也有強化作用。開發者在編寫用例時常犯的錯誤是把端口外邊的技術細節包含在用例里,這樣的用例易讀性差,乏味,脆弱,難于維護。使用六邊形架構后,編寫用例應該以應用的邊界為準。用例要明確應用能夠支持的功能和事件,而不用關心外部的技術是怎么樣的。
如何使用端口取決于個人經驗。一種極端的情況,每個用例都被賦予一個端口,這樣應用里就會有成百上千的端口。另一種情況是,把左右兩邊的端口分別合并起來,這樣就只剩兩個端口了。這兩種情況都不是最理想的。一些大家所熟知的應用是這樣的:
- 天氣預報系統有四個端口:天氣預報源,管理員,訂閱者,訂閱者數據庫。
- 咖啡機控制器有四個端口:用戶,包含菜單和價格的數據庫,調配師,硬幣盒。
- 醫藥系統有三個端口:護士,處方數據庫,藥劑師。
這些實例沒有拘泥于端口的數量,一切都是為了業務系統服務的。一般傾向于選擇更少的端口,實際上是另一種對分層模型的界定。
原文中談到的一個真實應用是這樣的,報警系統從國家天氣服務中心獲取地震,龍卷風,火災和洪水的預警,然后通過電話或語音留言通知人們。如果遵循技術與業務目標相關聯的原則,可以設計成一個接口用于接收來自預報源的數據,一個用來向語言留言機發送通知,一個GUI管理界面,以及一個獲取訂閱者數據的數據庫接口。當希望向系統里增加一些接口的時候,比如一個來自天氣服務中心的http接口,或者一個到訂閱者的郵件接口,還要考慮怎么讓應用套件滿足客戶定制化需求。這樣就會會陷入維護和測試的惡夢,因為要為不同的定制需求開發不同的版本。
經過系統接口設計的調整,新的六邊形架構面向的是業務目標,而不是具體的技術,而且把所有的具體技術換成了適配器。這樣,很快就把http和郵件接口加入到了系統中,把應用部署成"headless"模式,并添加了一個適配器,可以按需通過API調用連接到其他應用上。最后,因為應用的獨立運行能力和各種適配器的存在,就可以使用單獨的自動化腳本進行回歸測試了。
一句話體會
六邊形架構的初衷是為了解決技術與業務系統的解耦合問題,以及技術與技術間的解耦合問題,這一架構從設計模式中來,從業務的實體服務出發,將面向接口的設計具體化的端口協議和適配器實現,將業務實體實現自服務的完備性,可以看作是微服務的一個理論基礎吧。
原文中的參考閱讀
FIT, A Framework for Integrating Testing: Cunningham, W., online at http://fit.c2.com, and Mugridge, R. and Cunningham, W., ‘’Fit for Developing Software’’, Prentice-Hall PTR, 2005.
The “Adapter” pattern: in Gamma, E., Helm, R., Johnson, R., Vlissides, J., “Design Patterns”, Addison-Wesley, 1995, pp. 139-150.
The “Pedestal” pattern: in Rubel, B., “Patterns for Generating a Layered Architecture”, in Coplien, J., Schmidt, D., “PatternLanguages of Program Design”, Addison-Wesley, 1995, pp. 119-150.
The “Checks” pattern: by Cunningham, W., online at http://c2.com/ppr/checks.html
The “Dependency Inversion Principle”: Martin, R., in “Agile Software Development Principles Patterns and Practices”, Prentice Hall, 2003, Chapter 11: “The Dependency-Inversion Principle”, and online at http://www.objectmentor.com/resources/articles/dip.pdf
The “Dependency Injection” pattern: Fowler, M., online at http://www.martinfowler.com/articles/injection.html
The “Mock Object” pattern: Freeman, S. online at http://MockObjects.com
The “Loopback” pattern: Cockburn, A., online at http://c2.com/cgi/wiki?LoopBack
“Use cases:” Cockburn, A., “Writing Effective Use Cases”, Addison-Wesley, 2001, and Cockburn, A., “Structuring Use Cases with Goals”, online at http://alistair.cockburn.us/crystal/articles/sucwg/structuringucswithgoals.htm
微信掃一掃關注該公眾號