總覽
六角體系結構是一種軟件體系結構,它使應用程序能夠由用戶,程序,自動測試或批處理腳本平等驅動,而且能夠獨立於其運行時目標系統進行開發。目的是建立一個無需用戶界面或數據庫便可運行的應用程序,以便咱們能夠對該應用程序運行自動迴歸測試,在運行時系統(例如數據庫)不可用時使用該應用程序,或無需用戶界面便可集成應用程序。java
動機
許多應用程序有兩個目的:用戶端和服務器端,一般以兩層,三層或n層體系結構設計。n層體系結構的主要問題是沒有認真對待層線,從而致使應用程序邏輯越過邊界泄漏。業務邏輯和交互之間的這種糾纏使不可能或很難擴展或維護應用程序。數據庫
例如,當應用程序業務邏輯未徹底隔離在其自身邊界內時,添加新的有吸引力的UI以支持新設備多是一項艱鉅的任務。此外,應用程序能夠有兩個以上的方面,這使得很難更好地適應一維圖層體系結構。服務器
六角形或端口和適配器或洋蔥結構解決了這些問題。在這種體系結構中,內部應用程序經過必定數量的端口與外部系統進行通訊。在這裏,術語「六角形」自己並不重要,而是代表了在應用程序中以均勻和對稱的方式插入端口和適配器的效果。主要思想是經過使用端口和適配器隔離應用程序域。測試
在端口和適配器周圍組織代碼
讓咱們構建一個小型的anagram應用程序,以展現如何在端口和適配器周圍組織代碼以表示應用程序內部和外部之間的交互。在左側,咱們有一個應用程序,例如控制檯或REST,而內部則是核心業務邏輯或域。anagram服務採用兩個字符串,並返回一個布爾值,該布爾值對應於兩個String參數是否彼此爲字母。在右側,咱們有服務器端或基礎結構,例如,一個用於記錄有關服務使用狀況的度量標準的數據庫。this
下面的Anagram應用程序源代碼顯示瞭如何在內部隔離核心域以及如何提供端口和適配器以與其進行交互。url
域層
域層表明應用程序的內部,並提供與應用程序用例進行交互的端口。spa
- IAnagramServicePort 接口定義了一個方法,該方法接受兩個String字並返回一個布爾值。
- AnagramService 實現該IAnagramServicePort接口並提供業務邏輯以肯定兩個String參數是否爲anagram。它還使用IAnagramMetricPort來將服務使用度量輸出到服務器端運行時外部實體(例如數據庫)。
應用層
應用程序層爲外部實體與域交互提供了不一樣的適配器。交互依賴項進入內部。設計
-
ConsoleAnagramAdaptor 使用IAnagramServicePort來與應用程序內的域進行交互。code
-
AnagramsController 還使用IAnagramServicePort與域進行交互。一樣,咱們能夠編寫更多的適配器,以容許各類外部實體與應用程序域進行交互。orm
基礎設施層
提供適配器和服務器端邏輯,以從右側與應用程序進行交互。服務器端實體(例如數據庫或其餘運行時設備)使用這些適配器與域進行交互。請注意,交互依賴項位於內部。
外部實體與應用程序交互
如下兩個外部實體使用適配器與應用程序域進行交互。如咱們所見,應用程序域是徹底隔離的,而且由它們平等地驅動,而無論外部技術如何。
這是一個使用適配器與應用程序域交互的簡單控制檯應用程序:
@Configuration public class AnagramConsoleApplication { @Autowired private ConsoleAnagramAdapter anagramAdapter; public static void main(String[] args) { Scanner scanner = new Scanner([http://System.in](https://link.zhihu.com/?target=http%3A//System.in)); String word1 = scanner.next(); String word2 = scanner.next(); boolean isAnagram = anagramAdapter.isAnagram(word1, word2); if (isAnagram) { System.out.println("Words are anagram."); } else { System.out.println("Words are not anagram."); } } }
這是一個簡單的測試腳本示例,該腳本使用REST適配器模擬用戶與應用程序域的交互。
@SpringBootTest @AutoConfigureMockMvc public class AnagramsControllerTest { private static final String URL_PREFIX = "/anagrams/"; @Autowired private MockMvc mockMvc; @Test public void whenWordsAreAnagrams_thenIsOK() throws Exception { String url = URL_PREFIX + "/Hello/hello"; this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isOk()) .andExpect(content().string(containsString("{\"areAnagrams\":true}"))); } @Test public void whenWordsAreNotAnagrams_thenIsOK() throws Exception { 19 String url = URL_PREFIX + "/HelloDAD/HelloMOM"; this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isOk()) .andExpect(content().string(containsString("{\"areAnagrams\":false}"))); } @Test public void whenFirstPathVariableConstraintViolation_thenBadRequest() throws Exception { String url = URL_PREFIX + "/11/string"; this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isBadRequest()).andExpect( content().string(containsString("string1"))); } @Test public void whenSecondPathVariableConstraintViolation_thenBadRequest() throws Exception { String url = URL_PREFIX + "/string/11"; this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isBadRequest()).andExpect( content().string(containsString("string2"))); } }
結論
使用端口和適配器,應用程序域在內部六邊形處被隔離,而且不管外部系統或技術如何,它均可以由用戶或自動測試腳本一樣驅動。