Hexagonal Architecture in Java
概述
六角体系结构是一种软件体系结构,它使应用程序可以由用户,程序,自动测试或批处理脚本平等驱动,并且可以独立于其运行时目标系统进行开发。 目的是创建一个无需用户界面或数据库即可运行的应用程序,以便我们可以对该应用程序运行自动回归测试,在运行时系统(例如数据库)不可用时使用该应用程序,或者 无需用户界面即可集成应用程序。
动机
许多应用程序有两个目的:用户端和服务器端,通常设计为两层,三层或n层体系结构。 n层体系结构的主要问题是没有认真对待层线,从而导致应用程序逻辑越过边界泄漏。 业务逻辑和交互之间的纠缠使不可能或很难扩展或维护应用程序。
例如,当应用程序业务逻辑未完全隔离在其自身边界内时,添加新的有吸引力的UI以支持新设备可能是一项艰巨的任务。 此外,应用程序可以有两个以上的方面,这使得很难更好地适应一维层体系结构,如下所示。
六角形或端口和适配器或洋葱结构解决了这些问题。 在这种体系结构中,内部应用程序通过一定数量的端口与外部系统进行通信。 在这里,术语"六角形"本身并不重要,而是表明了以统一和对称的方式根据应用程序的需要插入端口和适配器的效果。 主要思想是通过使用端口和适配器隔离应用程序域。
在端口和适配器周围组织代码
让我们构建一个小型的anagram应用程序,以展示如何在端口和适配器周围组织代码以表示应用程序内部与外部之间的交互。 在左侧,我们有一个应用程序,例如控制台或REST,而内部则是核心业务逻辑或域。 anagram服务接受两个String,并返回一个布尔值,该布尔值对应于两个String参数是否彼此为字谜。 在右侧,我们拥有服务器端或基础结构,例如,一个用于记录有关服务使用情况的度量标准的数据库。
下面的Anagram应用程序源代码显示了如何在内部隔离核心域以及如何提供端口和适配器以与其进行交互。
域层
域层代表应用程序的内部,并提供与应用程序用例进行交互的端口。
应用层
应用程序层为外部实体与域交互提供了不同的适配器。 交互依赖项进入内部。
基础设施层
提供适配器和服务器端逻辑,以从右侧与应用程序进行交互。 服务器端实体(例如数据库或其他运行时设备)使用这些适配器与域进行交互。 请注意,交互依赖项位于内部。
外部实体与应用程序交互
以下两个外部实体使用适配器与应用程序域进行交互。 如我们所见,应用程序域是完全隔离的,并且由它们平等地驱动,而不管外部技术如何。
这是一个使用适配器与应用程序域交互的简单控制台应用程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Configuration public class AnagramConsoleApplication { @Autowired private ConsoleAnagramAdapter anagramAdapter; public static void main(String[] args) { Scanner scanner = new Scanner(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适配器模拟用户与应用程序域的交互。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | @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 { 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"))); } } |
结论
使用端口和适配器,应用程序域在内部六边形处隔离,并且可以由用户或自动测试脚本来平等地驱动,而无需考虑外部系统或技术。