Архитектура: Hexagonal

Hexagonal


Состоит из нескольких слоев, каждый из которых в свою очередь, выглядит как шестиугольник Hexagon.

Корневым является Domain Hexagon. Призван решить основную проблему приложения. Основными элементами являются сущности (все к чему могут быть применены уникальные идентификаторы) и значения (immutable объекты, которые заполняют сущности данными).

Следующим является Application Hexagon, который выполняет различные процессы и управляет бизнес-требованиями при работе с объектами их Domain Hexagon. Использует Port и Use-Case для выполнения требуемых действий.

Framework Hexagon. Определяет как приложение общается с вшеним миром. Контролирует работу с внешними сервисами, БД, стриминговыми сервисами и т.д.

Domain Hexagon

Под сущностями (Entities) следует понимать объекты, которые хранят в себе данные и состояния сущностей.

1
2
3
4
public class Person {
String username;
PersonState state;
}

Под значениями (Values) следует понимать данные, которые хранятся в сущностях, а так же те состояния, которые сущности могут принимать (например, enum).

1
2
3
enum PersonState {
ACTIVE, INACTIVE;
}

В основе Domain Hexagon лежит архитектура DDD (Domain Driven Design).

Domain Service

В объектах слоя Domain (Domain Hexogon) не всегда возможно поместить всю бизнес логику, соответствующую бизнес-кейсу. Например, если задействовано несколько сущностей, зависимых друг от друга, но в связи с ограничениями архитектуры DDD (Domain Driven Design), сущности могут изменяться только в рамках своих Domain реализаций.

Выходом из подобной ситуации служит создание Domain Service, которые реализуют бизнес-кейс, объединяющий в себе несколько сущностей и так или иначе затрагивающий их. На вход сервису может приходить набор сущностей, “завернутый”” в обертку.

Valuable Objects

Далеко не всем объектам надо выдавать идентификаторы. При этом, иногда обертка в виде некой сущности, хранящей и передающей данные, может оказаться очень полезной:

1
2
3
4
5
6
7
8
9
10
11
12
public class PersonName {
private final String firstname;
private final String secondname;
private final String surname;
...
}

public class Person {
private final UUID id;
private final PersonName name;
...
}

Отличительной особенностью данного типа объектов является отсутствие идентификаторов и они всегда immutable.

Application Hexagon

Объединяет в себе набор представлений включая Use-Case, Input и Output Ports. Фактически, этот hexagon реализует оргику приложения абстрагируясь от применяемых технологий, оперируя объектами из Domain Hexagon.

Use-Case

Описывает поведение приложения при определенном сценарии, отвечает на вопрос - “Что приложение делает (но предоставляет решение для данного вопроса)?” Может быть реализовано в виде интерфейса, например:

1
2
3
public interface FindPersonUseCase {
List<Person> listPersons(Predicate<Person> filter);
}

Output Port

Компонент, который предоставляет данные из внешнего (для Application Hexagon) мира. Например, это может быть, пользовательский ввод, база данных, внешний сервис и т.д.

1
2
3
public interface PersonResource {
Set<Person>listAll();
}

Input Port

Компонент, который реализует Use-Case (или несколько).

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PersonScanner implements FindPersonUseCase {
PersonResource personResource;

public PersonScanner(PersonResource personResource) {
this.personResource =personResource;
}

@Override
public List<Person> listPersons(Predicate<Person> filter) {
var persons = personResource.listAll();
return persons.stream.filter(filter).collect(Collectors.toList());
}
}

Framework Hexagon

Hexagon, который определяет с использованием каких технологий будет осуществляться взаимодействие с приложением. Содержит в себе 2 вида адартеров - Input и Output.

Input Adapter

Загружает данные из внешних источников и передает их в Domain Hexagon в соответствующем структурированном виде. Зачастую является катализатором различных действий и событий, проиходящих в приложении.

1
2
3
4
5
6
7
8
public class PersonController {
PersonScanner scanner;

public void listAllByType(String type) {
final State state = State.of(type);
return Scanner.listPersons(person -> person.getState().equals(state));
}
}

Output Adapter

Компонент, который выполняет действия по передаче/получению объектов Domain Hexagon в/вне приложения. Например, это может быть файл, база данных, внешний сервис и т.д.

1
2
3
4
5
6
7
8
9
10
public DbPersonResource implements PersonResource {
@Override
public Set<Person> listAll() {
try {
...
} catch (SQLException sqle) {
...
}
}
}

Плюсы Hexagonal архитектуры

  • Стабильность к изменениями. Ввиду большой изолированности от внешних зависимостей, ядро приложения является независимым и безопасным к изменениям внешней среды;
  • Поддерживаемость;
  • Доступность к тестированию. Поскольку нет (или их очень мало) внешних зависимостей, ядро приложения может тестироваться и не потребует использования каких-либо внешних дополнений. Вместе с этим, к тестированию может быть подключены дополнительные надстройки.

Особенности архитектуры

Для Hexagonal архитектуры моветоном является перекладывание идентификации сущностей на какие-то внешние сервисы. Например, идентификатором часто может выступать последовательный инкремент, определяемый базой данных. Это удобно с точки зрения реализации, но что делать если от данной реализации БД придется отказаться?

По этой причине, “стандартом”” для идентификации является UUID. Доступно несколько способов генерации UUID:

  • Time-based;
  • Distributed Computer Environment (DCE) security;
  • Name-based;
  • Случайная генерация.

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

Генерация может быть добавлена в отдельный класс, например, так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PersonId {
private final UUID id;

private PersonId(UUID id) {
this.id = id;
}

public static PersonId generateByName(String somename) {
return new PersonId(UUID.fromString(somename));
}

public static PersonId generateRandom() {
return new PersonId(UUID.randomUUID());
}
}
 Comments
Comment plugin failed to load
Loading comment plugin