Рубрики
Без рубрики

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

В эволюции архитектуры программного обеспечения центральное место занимала слабая связь. Существует… С пометкой архитектура, программирование, java, учебное пособие.

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

Шестиугольная архитектура – это еще одно достижение в слабо связанных архитектурах. Она возникла примерно в начале перехода к предметно-ориентированным проектам и легла в основу дальнейших достижений в области разработки программного обеспечения.

Вступление

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

Пользовательские интерфейсы

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

Бизнес-логика

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

Ниже приведено небольшое словесное облако обязанностей уровня бизнес-логики. Обязанности могут варьироваться в зависимости от одного варианта использования к другому.

Банковские услуги

Это сервисы, которые поддерживают бизнес-логику. Каждый из них служит определенной цели и предоставляет данные/услуги приложению. Они взаимодействуют со слоем бизнес-логики и могут быть заменены до тех пор, пока поддерживается контракт связи между двумя уровнями. Несколько примеров:

  1. Источники данных
  2. Кэширование в стороне серверов, таких как Redis
  3. Службы уведомлений
  4. Еще одна услуга, подобная платежному шлюзу
  5. В контексте микросервисов – другой микросервис.

Намерения и принципы

Цель состоит в том, чтобы сделать ядро нашего приложения невосприимчивым к изменениям в связи с другими уровнями. Эти проблемы должны решаться на границе нашего шестиугольника.

Порты

Порты – это то, с чем взаимодействует наше основное приложение. Порты остаются согласованными для внутреннего приложения независимо от того, что происходит за их пределами. Это интерфейсы , с которыми взаимодействуют внутренние компоненты, не зная, что к ним подключено.

Адаптеры

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

Примечание – Гексагональный – это просто термин, который закрепился в архитектуре для простоты. Его не следует неправильно понимать как уровень бизнес-логики, имеющий 6 портов. У многоугольника может быть гораздо больше сторон в соответствии с услугами, необходимыми для подключения

Пример

Что касается приведенной выше диаграммы, представьте себе небольшое приложение – REST API, которое занимается операциями, связанными с пользователем.

Порт интерфейса – запросы могут поступать с веб-сайта или приложения. Они могут иметь разные параметры и могут ожидать разных форматов ответов. Мы создаем адаптер для каждого участника интерфейса.

  • Он получает запрос
  • преобразует его в согласованный формат, определенный портом
  • передает его во внутреннее приложение.

Когда запрос достигает внутреннего приложения, он согласуется с интерфейсом, предоставляемым портом. Приложение работает с ним и возвращает ответ в формате, который ожидает порт.

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

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

Давайте рассмотрим пример использования порта базы данных с помощью кода.

Давайте посмотрим на какой-нибудь код

Дизайнерское намерение – Мы хотим начать с базы данных MySQL, но мы не уверены, потребуется ли в будущем другая база данных. Наш код должен обеспечивать легкую замену баз данных при необходимости.

Порт (Интерфейс)

Мы предоставляем интерфейс для взаимодействия с нашим ядром. Интерфейс выполняет операции crud.

public interface UserRepository {
    void save(User o);
    void delete(User o);
    void update(User o);
    User find(int id);
}

Адаптер

Адаптер базы данных MySQL

public class MySqlDatabaseRepository implements UserRepository {
    @Override
    public void save(User User) {
        System.out.println("Saving to database");
    }

    @Override
    public void delete(User User) {
        System.out.println("Deleting from database");
    }

    @Override
    public void update(User User) {
        System.out.println("Updating database");
    }

    @Override
    public User find(int id) {
        System.out.println("Finding in database");
        return null;
    }
}

Взаимодействие с базами данных

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

Давайте рассмотрим одну из наших основных услуг. Приведенный ниже класс предназначен для получения сведений о пользователе – как основных, так и полных.

public class UserDetailsClient {

    private UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public BasicDetails getBasicDetails(int id) {
        User user = userRepository.find(id);
        return new BasicDetails(user.getName(), user.getEmail());
    }

    public FullDetails getFullDetails(int id) {
        User user = userRepository.find(id);
        return new FullDetails(user.getName(), user.getEmail(), user.getAddress());
    }

}

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

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

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

В моем случае внешний слой, который пытается получить сведения о пользователе, инициализирует User Details Client путем передачи требуемого адаптера. Например, для

UserDetailsClient userDetailsClient = new UserDetailsClient(new MySqlDatabaseRepository());
userDetailsClient.getBasicDetails(userId);

Обмен базами данных

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

Адаптер MongoDB

public class MongoDbRepository implements UserRepository {
    @Override
    public void save(User User) {
        System.out.println("Saving User to mongoDb");
    }

    @Override
    public void delete(User id) {
        System.out.println("Deleting User from mongoDb");
    }

    @Override
    public void update(User User) {
        System.out.println("Updating User in mongoDb");
    }

    @Override
    public User find(int id) {
        System.out.println("Finding User in mongoDb");
        return null;
    }
}

Чтобы использовать базу данных MongoDB, единственное изменение, которое требуется, – это способ инициализации клиента User Details . Наш код вызова изменяется следующим образом:

UserDetailsClient userDetailsClient = new UserDetailsClient(new MongoDbRepository());
userDetailsClient.getBasicDetails(userId);

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

  1. Заменяемые компоненты – как мы можем видеть на уровне базы данных. Также могут быть и другие сервисы по той же схеме. Например, я мог бы иметь службы уведомлений и переключаться между электронными письмами и SMS, когда это необходимо.
  2. Разделение бизнес-логики – При правильной реализации шестиугольная архитектура не представляет угрозы для бизнес-правил в ядре приложения при изменении внешних уровней.
  3. Упрощенное тестирование через порты – Тестирование основного приложения может выполняться вокруг портов. При необходимости макетные ресурсы могут быть введены с использованием собственных адаптеров, чтобы упростить модульное тестирование без баз данных.

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

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

Спасибо вам за чтение. Надеюсь, вам понравилась статья. Пожалуйста, оставляйте любые отзывы и предложения в комментариях. Если вы хотите связаться со мной, вы можете найти меня на Твиттер

Забавный факт : Примеры кода создаются вторым пилотом Github с небольшим ручным вмешательством.

Оригинал: “https://dev.to/abh1navv/hexagonal-architecture-3ocl”