Шаблоны проектирования программного обеспечения в действии

1. Обзор

Гексагональная архитектура — это шаблон проектирования программного обеспечения, впервые представленный Алистэром Кокберном. Он предоставляет продуманный способ проектирования архитектуры веб-приложений с использованием Java или фреймворков, связанных с Java, таких как Spring. В этой статье мы рассмотрим гексагональную архитектуру в Java и покажем ее использование на практическом примере, реализованном с использованием фреймворка Spring.

2. Шестиугольная архитектура

Основная идея гексагональной архитектуры заключается в четком разделении логики с помощью принципа разделения интерфейса. Более того, шестиугольная архитектура создает слабосвязанные приложения благодаря концепции портов и адаптеров. Здесь порты относятся к интерфейсу, а адаптеры относятся к его классу реализации (как показано на изображении ниже).

На приведенной выше диаграмме показаны различные конечные точки в типичной шестиугольной архитектуре. Через прикладной уровень пользователь взаимодействует с базовой логикой приложения через порты и адаптеры. Уровень приложения состоит из клиента с графическим интерфейсом, клиента социальных сетей, вызовов HTTP/API и т. д. Точно так же другая половина приложения представляет собой уровень сохранения или инфраструктуры, через который данные передаются другим компонентам. Уровень постоянства содержит внешние компоненты, такие как базы данных, почтовые сообщения и очереди сообщений и т. д.

3. Пример

Чтобы лучше понять шестиугольную архитектуру, давайте рассмотрим пример приложения для доставки пиццы. В этом приложении у нас будут следующие функции:

  • Список всех доступных пицц
  • Вставьте новую пиццу в базу данных
  • Получить пиццу по имени

4. Объект домена

Объект предметной области или объекта является основной частью шестиугольной архитектуры. Он может иметь как состояние, так и поведение. Этот объект не имеет никакой зависимости ни от одного из компонентов приложения. Любое изменение объекта предметной области произойдет, если изменится само бизнес-требование.

public class Pizza {
	private String name;
	private int price;
	private String[] toppings;
	// code for getters & setters
}

5. Порты

Порты в шестиугольной архитектуре относятся к интерфейсам, которые разрешают входящий или исходящий поток. Входящий порт предоставляет доступ к основным функциям приложения внешнему миру. Например, вызов API к интерфейсу службы.

Давайте определим интерфейс PizzaService, который будет предоставлять свои функции внешним компонентам (например, вызовам API). Это наш входящий порт.

public interface PizzaService {
       public void createPizza(Pizza pizza);
       public Pizza getPizza(String name);
       public List<Pizza> laodPizza();
}

Точно так же исходящие порты используются для подключения к некоторым внешним репозиториям, таким как базы данных.

Давайте определим PizzaRepository, который будет обращаться к внешней постоянной системе (БД).

public interface PizzaRepo {
       public void createPizza(Pizza pizza);
       public Pizza getPizza(String name);
       public List<Pizza> getAllPizza();
}

6. Адаптеры

Адаптеры относятся к классам реализации соответствующих портов в шестиугольной архитектуре. Они являются внешней частью приложения (например, графический интерфейс, вызовы API, веб-просмотры, Dao и т. д.) и взаимодействуют с приложением через входящие и исходящие порты соответственно. Кроме того, адаптеры упрощают замену определенного слоя приложения. В зависимости от требуемых изменений нам просто нужно добавить адаптер, реализующий входной или выходной порт.

6.1 Первичные адаптеры

Их также называют входными или управляющими адаптерами, поскольку они управляют приложением, вызывая основную часть приложения с использованием входящих портов.

Давайте определим PizzaRestContoller как контроллер REST в качестве нашего основного адаптера. Он предоставляет конечные точки для создания и получения пиццы, а также реализует PizzaRestUI (веб-просмотр). Кроме того, он использует PizzaService (входящий порт) для вызова различных методов.

@RestController
@RequestMapping(value="/pizza")
public class PizzaRestController implements PizzaRestUI {
       @Autowired
       private PizzaService pizzaService;
       @Override
       public void createPizza(@RequestBody Pizza pizza) {
             pizzaService.createPizza(pizza);
       }
       @Override
       public Pizza getPizza(@PathVariable String name) {
             return pizzaService.getPizza(name);
       }
       @Override
       public List<Pizza> listPizza() {
             return pizzaService.laodPizza();
       }
}

6.2 Вторичные адаптеры

Они известны как выходные или управляемые адаптеры и реализуют интерфейс исходящего порта. Эти адаптеры обеспечивают реализацию для доступа к вторичным компонентам приложения, таким как базы данных, очереди сообщений и т. д. В то время как уровень службы реализует входной порт, выходной порт реализуется с использованием уровня сохраняемости.

В нашем случае PizzaRepoImpl — это исходящий адаптер, реализующий PizzaRepo (исходящий порт).

@Repository
public class PizzaRepoImpl implements PizzaRepo {
       private Map<String, Pizza> pizzaStore = new HashMap<String, Pizza>();
       @Override
       public void createPizza(Pizza pizza) { 
             pizzaStore.put(pizza.getName(), pizza);
       }
       @Override
       public Pizza getPizza(String name) {
             return pizzaStore.get(name);
       }
       @Override
       public List<Pizza> getAllPizza() {
             return pizzaStore.values().stream().collect(Collectors.toList());
       }
}

Затем мы можем протестировать конечные точки GET и POST, используя любой инструмент тестирования API, такой как Postman.

Мы можем протестировать конечную точку POST через http://localhost:8080/pizza-service/pizza/.

{
    "name" : "Margherita",
    "price": "25",
    "toppings" : ["tomato","onion","cucumber","jalapeno"]
}

Точно так же мы можем протестировать конечную точку GET через http://localhost:8080/pizza-service/pizza/Margherita.

{
    "name": "Margherita",
    "price": 25,
    "toppings": [
        "tomato",
        "onion",
        "cucumber",
        "jalapeno"
    ]
}

7. Преимущества

Шестиугольная архитектура имеет ряд преимуществ по сравнению с традиционной многоуровневой архитектурой, например:

  • Это упрощает проектирование архитектуры за счет разделения внутренних и внешних компонентов приложения.
  • Основная бизнес-логика отделена от любых внешних зависимостей, что обеспечивает высокую степень разделения.
  • Архитектура на основе портов позволяет нашему приложению легко адаптироваться к новым каналам или использовать новые протоколы связи, что полезно при разработке приложений, управляемых доменом.

8. Заключение

В этой статье мы узнали о гексагональной архитектуре в Java на простом примере, реализованном в среде Spring. Кроме того, мы обсудили некоторые преимущества использования гексагональной архитектуры по сравнению с традиционной многоуровневой архитектурой.

Код для данного примера доступен на Github.