Что такое гексагональная архитектура (Hexagonal Architecture)?

«Что такое гексагональная архитектура (Hexagonal Architecture)?» — вопрос из категории Архитектура, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Гексагональная архитектура (Порты и адаптеры) — это архитектурный паттерн, который изолирует бизнес-логику (ядро приложения) от внешних зависимостей (БД, UI, внешние API), делая её независимой и легко тестируемой.

Ключевые компоненты:

  1. Ядро (Domain): Содержит чистую бизнес-логику и модели. Ничего не знает о внешнем мире.
  2. Порты (Ports): Интерфейсы, которые определяют как ядро может взаимодействовать с внешним миром.
    • Входящие порты (Primary/Driving Ports): Для случаев, когда ядро вызывают (например, UserService).
    • Исходящие порты (Secondary/Driven Ports): Для случаев, когда ядро обращается вовне (например, UserRepository).
  3. Адаптеры (Adapters): Реализации портов для конкретных технологий.
    • Входящие адаптеры: Контроллеры REST API, CLI, Web-формы.
    • Исходящие адаптеры: Реализации репозиториев для JPA, вызовы внешних REST-сервисов.

Схема потока данных: Внешний мир -> [Входящий адаптер] -> [Входящий порт] -> [Ядро] -> [Исходящий порт] -> [Исходящий адаптер] -> Внешний мир

Пример структуры:

// 1. ЯДРО: Доменный объект
public class User {
    private Long id;
    private String name;
}

// 2. ЯДРО: Исходящий порт (интерфейс репозитория)
public interface UserRepositoryPort {
    User findById(Long id);
}

// 3. ЯДРО: Входящий порт (интерфейс сервиса)
public interface UserServicePort {
    User getUser(Long id);
}

// 4. ЯДРО: Реализация бизнес-логики
@Service
public class UserService implements UserServicePort {
    private final UserRepositoryPort repository; // Зависимость от порта!
    public User getUser(Long id) { return repository.findById(id); }
}

// 5. АДАПТЕР: JPA-реализация исходящего порта
@Repository
public class JpaUserRepositoryAdapter implements UserRepositoryPort {
    @PersistenceContext
    private EntityManager em;
    public User findById(Long id) { return em.find(User.class, id); }
}

// 6. АДАПТЕР: REST-контроллер (входящий адаптер)
@RestController
public class UserController {
    private final UserServicePort userService; // Зависимость от порта!
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) { return userService.getUser(id); }
}

Главное преимущество: Технологии становятся заменяемыми деталями. Чтобы сменить БД с PostgreSQL на MongoDB, нужно лишь написать новый MongoUserRepositoryAdapter, не трогая ядро.