总览
六角体系结构是一种软件体系结构,它使应用程序能够由用户,程序,自动测试或批处理脚本平等驱动,而且能够独立于其运行时目标系统进行开发。目的是建立一个无需用户界面或数据库便可运行的应用程序,以便咱们能够对该应用程序运行自动回归测试,在运行时系统(例如数据库)不可用时使用该应用程序,或无需用户界面便可集成应用程序。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"))); } }
结论
使用端口和适配器,应用程序域在内部六边形处被隔离,而且不管外部系统或技术如何,它均可以由用户或自动测试脚本一样驱动。