Hexagonal
Состоит из нескольких слоев, каждый из которых в свою очередь, выглядит как шестиугольник Hexagon.
Корневым является Domain Hexagon. Призван решить основную проблему приложения. Основными элементами являются сущности (все к чему могут быть применены уникальные идентификаторы) и значения (immutable объекты, которые заполняют сущности данными).
Следующим является Application Hexagon, который выполняет различные процессы и управляет бизнес-требованиями при работе с объектами их Domain Hexagon. Использует Port и Use-Case для выполнения требуемых действий.
Framework Hexagon. Определяет как приложение общается с вшеним миром. Контролирует работу с внешними сервисами, БД, стриминговыми сервисами и т.д.
Domain Hexagon
Под сущностями (Entities) следует понимать объекты, которые хранят в себе данные и состояния сущностей.
1 | public class Person { |
Под значениями (Values) следует понимать данные, которые хранятся в сущностях, а так же те состояния, которые сущности могут принимать (например, enum
).
1 | enum PersonState { |
В основе Domain Hexagon лежит архитектура DDD (Domain Driven Design).
Domain Service
В объектах слоя Domain (Domain Hexogon) не всегда возможно поместить всю бизнес логику, соответствующую бизнес-кейсу. Например, если задействовано несколько сущностей, зависимых друг от друга, но в связи с ограничениями архитектуры DDD (Domain Driven Design), сущности могут изменяться только в рамках своих Domain реализаций.
Выходом из подобной ситуации служит создание Domain Service, которые реализуют бизнес-кейс, объединяющий в себе несколько сущностей и так или иначе затрагивающий их. На вход сервису может приходить набор сущностей, “завернутый”” в обертку.
Valuable Objects
Далеко не всем объектам надо выдавать идентификаторы. При этом, иногда обертка в виде некой сущности, хранящей и передающей данные, может оказаться очень полезной:
1 | public class PersonName { |
Отличительной особенностью данного типа объектов является отсутствие идентификаторов и они всегда immutable.
Application Hexagon
Объединяет в себе набор представлений включая Use-Case, Input и Output Ports. Фактически, этот hexagon реализует оргику приложения абстрагируясь от применяемых технологий, оперируя объектами из Domain Hexagon.
Use-Case
Описывает поведение приложения при определенном сценарии, отвечает на вопрос - “Что приложение делает (но предоставляет решение для данного вопроса)?” Может быть реализовано в виде интерфейса, например:
1 | public interface FindPersonUseCase { |
Output Port
Компонент, который предоставляет данные из внешнего (для Application Hexagon) мира. Например, это может быть, пользовательский ввод, база данных, внешний сервис и т.д.
1 | public interface PersonResource { |
Input Port
Компонент, который реализует Use-Case (или несколько).
1 | public class PersonScanner implements FindPersonUseCase { |
Framework Hexagon
Hexagon, который определяет с использованием каких технологий будет осуществляться взаимодействие с приложением. Содержит в себе 2 вида адартеров - Input и Output.
Input Adapter
Загружает данные из внешних источников и передает их в Domain Hexagon в соответствующем структурированном виде. Зачастую является катализатором различных действий и событий, проиходящих в приложении.
1 | public class PersonController { |
Output Adapter
Компонент, который выполняет действия по передаче/получению объектов Domain Hexagon в/вне приложения. Например, это может быть файл, база данных, внешний сервис и т.д.
1 | public DbPersonResource implements PersonResource { |
Плюсы Hexagonal архитектуры
- Стабильность к изменениями. Ввиду большой изолированности от внешних зависимостей, ядро приложения является независимым и безопасным к изменениям внешней среды;
- Поддерживаемость;
- Доступность к тестированию. Поскольку нет (или их очень мало) внешних зависимостей, ядро приложения может тестироваться и не потребует использования каких-либо внешних дополнений. Вместе с этим, к тестированию может быть подключены дополнительные надстройки.
Особенности архитектуры
Для Hexagonal архитектуры моветоном является перекладывание идентификации сущностей на какие-то внешние сервисы. Например, идентификатором часто может выступать последовательный инкремент, определяемый базой данных. Это удобно с точки зрения реализации, но что делать если от данной реализации БД придется отказаться?
По этой причине, “стандартом”” для идентификации является UUID
. Доступно несколько способов генерации UUID
:
- Time-based;
- Distributed Computer Environment (DCE) security;
- Name-based;
- Случайная генерация.
Минусом выбора идентификации с использованием UUID
является увеличенный размер данных, и усложненная индексация сущностей.
Генерация может быть добавлена в отдельный класс, например, так:
1 | class PersonId { |