обзор
Шестиугольная архитектура — это программная архитектура, которая позволяет приложению одинаково управляться пользователями, программами, автоматизированными тестами или пакетными сценариями и разрабатываться изолированно от целевой системы во время выполнения. Намерение состоит в том, чтобы создать приложение для работы без пользовательского интерфейса или базы данных, чтобы мы могли запустить автоматический регрессионный тест для приложения, работать с приложением, когда система времени выполнения, такая как база данных, недоступна, или интегрировать приложения без пользовательского интерфейса.
мотивация
Многие приложения имеют два конца: пользовательскую и серверную, часто разработанные в двух, трех или n-уровневой архитектуре. Основная проблема n-уровневой архитектуры заключается в том, что линии слоев не воспринимаются всерьез, что вызывает утечку логики приложения через границы. Эта запутанность между бизнес-логикой и взаимодействием делает невозможным или трудным расширение или сопровождение приложения.
Например, добавление нового привлекательного пользовательского интерфейса для поддержки новых устройств может быть сложной задачей, когда бизнес-логика приложения не полностью изолирована на своей собственной границе. Кроме того, приложения могут иметь более двух сторон, что затрудняет их корректное размещение в архитектуре одномерного уровня, как показано ниже.
Гексагональные или порты и адаптеры или луковая архитектура решают эти проблемы. В этой архитектуре приложение во внутренней связи связывается через некоторое количество портов с системами снаружи. Здесь сам термин «гексагональный» не важен, скорее он демонстрирует эффект от вставки портов и адаптеров по мере необходимости в приложении единообразным и симметричным способом. Основная идея состоит в том, чтобы изолировать домен приложения с помощью портов и адаптеров.
Организация кода вокруг портов и адаптеров
Давайте создадим небольшое приложение анаграммы, чтобы показать, как организовать код вокруг портов и адаптеров, чтобы представить взаимодействие между приложением и за его пределами. Слева у нас есть приложение, такое как консоль или REST, а внутри находится основная бизнес-логика или домен. Служба анаграмм принимает две строки и возвращает логическое значение, соответствующее тому, являются ли два аргумента строки анаграммами друг друга. Справа у нас есть серверная часть или инфраструктура, например, база данных для регистрации метрик об использовании сервиса.
Исходный код приложения Anagram ниже показывает, как основной домен изолирован внутри, а также предусмотрены порты и адаптеры для взаимодействия с ним.
Уровень домена
Уровень домена представляет собой внутреннюю часть приложения и предоставляет порты для взаимодействия со случаями использования приложения.
- IAnagramServicePortИнтерфейс определяет один метод, который принимает два строковых слова и возвращает логическое значение.
- AnagramServiceреализует- IAnagramServicePortинтерфейс и предоставляет бизнес-логику, чтобы определить, являются ли два аргумента String анаграммами. Он также использует- IAnagramMetricPortдля вывода метрики использования сервиса на стороне сервера во время выполнения внешних объектов, таких как база данных.
Уровень приложений
Прикладной уровень предоставляет различные адаптеры для внешних объектов для взаимодействия с доменом. Зависимость взаимодействия идет внутрь.
- ConsoleAnagramAdaptorиспользует- IAnagramServicePortдля взаимодействия с доменом внутри приложения.
- AnagramsControllerтакже использует- IAnagramServicePortдля взаимодействия с доменом. Точно так же мы можем написать гораздо больше адаптеров, чтобы позволить различным внешним объектам взаимодействовать с областью приложения.
Уровень инфраструктуры
Предоставьте адаптеры и серверную логику для взаимодействия с приложением с правой стороны. Объекты на стороне сервера, такие как база данных или другие устройства времени выполнения, используют эти адаптеры для взаимодействия с доменом. Обратите внимание, что зависимость взаимодействия идет внутрь.
Внешние объекты, взаимодействующие с приложением
Следующие два внешних объекта используют адаптеры для взаимодействия с доменом приложения. Как мы видим, область приложения полностью изолирована и одинаково управляется ими независимо от внешних технологий.
Вот простое консольное приложение, взаимодействующее с доменом приложения с помощью адаптера:
Джава
x
1
    
2
    public class AnagramConsoleApplication {
3
  
4
        
5
        private ConsoleAnagramAdapter anagramAdapter;
6
    
7
        public static void main(String[] args) {
8
            Scanner scanner = new Scanner(System.in);
9
            String word1 = scanner.next();
10
            String word2 = scanner.next();
11
            boolean isAnagram = anagramAdapter.isAnagram(word1, word2);
12
            if (isAnagram) {
13
                System.out.println("Words are anagram.");
14
            } else {
15
                System.out.println("Words are not anagram.");
16
            }
17
        }
18
    }
Вот пример простого тестового сценария, который проверяет взаимодействие пользователя с доменом приложения с помощью адаптера REST.
Джава
x
1
    
2
    
3
    public class AnagramsControllerTest {
4
    
5
        private static final String URL_PREFIX = "/anagrams/";
6
    
7
        
8
        private MockMvc mockMvc;
9
    
10
        
11
        public void whenWordsAreAnagrams_thenIsOK() throws Exception {
12
            String url = URL_PREFIX + "/Hello/hello";
13
         this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isOk())
14
            .andExpect(content().string(containsString("{\"areAnagrams\":true}")));
15
        }
16
    
17
        
18
        public void whenWordsAreNotAnagrams_thenIsOK() throws Exception {
19
            String url = URL_PREFIX + "/HelloDAD/HelloMOM";
20
            this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isOk())
21
             .andExpect(content().string(containsString("{\"areAnagrams\":false}")));
22
        }
23
    
24
25
        
26
        public void whenFirstPathVariableConstraintViolation_thenBadRequest() throws Exception {
27
            String url = URL_PREFIX + "/11/string";
28
            this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isBadRequest()).andExpect(
29
                    content().string(containsString("string1")));
30
        }
31
    
32
        
33
        public void whenSecondPathVariableConstraintViolation_thenBadRequest() throws Exception {
34
            String url = URL_PREFIX + "/string/11";
35
            this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isBadRequest()).andExpect(
36
                    content().string(containsString("string2")));
37
        }
38
}
39
Заключение
Используя порты и адаптеры, домен приложения изолирован во внутреннем шестиграннике, и он может в равной степени управляться пользователем или сценариями автоматического тестирования независимо от внешней системы или технологии.