В эволюции архитектуры программного обеспечения/| слабая связь была в центре внимания. Особое внимание уделяется разделению приложений на компоненты, которые можно переключать, заменять или обновлять, не затрагивая зависимые компоненты.
Шестиугольная архитектура – это еще одно достижение в слабо связанных архитектурах. Она возникла примерно в начале перехода к предметно-ориентированным проектам и легла в основу дальнейших достижений в области разработки программного обеспечения.
Вступление
Шестиугольная архитектура разделена на три части и определяет строгие роли, которые эти части играют в приложении.
Пользовательские интерфейсы
У серверной части может быть множество пользовательских интерфейсов – мобильные приложения, веб-приложения, программное обеспечение для настольных компьютеров и т.д. Все они будут получать свои ресурсы с уровня бизнес-логики.
Бизнес-логика
Это составляет основу приложения. Его цель состоит в том, чтобы удовлетворить запросы пользовательских интерфейсов. Основываясь на запросе, он запускает некоторую пользовательскую логику, получает ресурсы, необходимые для выполнения запроса, и отвечает в согласованном формате ответа.
Ниже приведено небольшое словесное облако обязанностей уровня бизнес-логики. Обязанности могут варьироваться в зависимости от одного варианта использования к другому.
Банковские услуги
Это сервисы, которые поддерживают бизнес-логику. Каждый из них служит определенной цели и предоставляет данные/услуги приложению. Они взаимодействуют со слоем бизнес-логики и могут быть заменены до тех пор, пока поддерживается контракт связи между двумя уровнями. Несколько примеров:
- Источники данных
- Кэширование в стороне серверов, таких как Redis
- Службы уведомлений
- Еще одна услуга, подобная платежному шлюзу
- В контексте микросервисов – другой микросервис.
Намерения и принципы
Цель состоит в том, чтобы сделать ядро нашего приложения невосприимчивым к изменениям в связи с другими уровнями. Эти проблемы должны решаться на границе нашего шестиугольника.
Порты
Порты – это то, с чем взаимодействует наше основное приложение. Порты остаются согласованными для внутреннего приложения независимо от того, что происходит за их пределами. Это интерфейсы , с которыми взаимодействуют внутренние компоненты, не зная, что к ним подключено.
Адаптеры
Порты остаются согласованными, но мы по-прежнему хотим иметь возможность подключать к ним несколько приложений, когда это необходимо. Эти приложения могут иметь разные потребности и могут не соответствовать интерфейсу, определяемому портами. Вот тут-то и пригодятся внешние адаптеры. Их цель состоит в том, чтобы преобразовать данные, предоставляемые внешними приложениями, в формат, удобоваримый для внутреннего приложения.
Примечание – Гексагональный – это просто термин, который закрепился в архитектуре для простоты. Его не следует неправильно понимать как уровень бизнес-логики, имеющий 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);
Преимущества
- Заменяемые компоненты – как мы можем видеть на уровне базы данных. Также могут быть и другие сервисы по той же схеме. Например, я мог бы иметь службы уведомлений и переключаться между электронными письмами и SMS, когда это необходимо.
- Разделение бизнес-логики – При правильной реализации шестиугольная архитектура не представляет угрозы для бизнес-правил в ядре приложения при изменении внешних уровней.
- Упрощенное тестирование через порты – Тестирование основного приложения может выполняться вокруг портов. При необходимости макетные ресурсы могут быть введены с использованием собственных адаптеров, чтобы упростить модульное тестирование без баз данных.
Хотя гексагональная архитектура не является чем-то, о чем явно думают при проектировании архитектуры приложения, она часто случайно используется во всех современных приложениях – особенно в мире Java, который вращается вокруг внедрения зависимостей и кодирования интерфейсов, а не реализаций.
В настоящее время большое внимание уделяется конфигурируемости и адаптивности приложений. Важно учитывать “шаблон портов и адаптеров” в процессе проектирования низкого уровня больше, чем при проектировании высокого уровня.
Спасибо вам за чтение. Надеюсь, вам понравилась статья. Пожалуйста, оставляйте любые отзывы и предложения в комментариях. Если вы хотите связаться со мной, вы можете найти меня на Твиттер
Забавный факт : Примеры кода создаются вторым пилотом Github с небольшим ручным вмешательством.
Оригинал: “https://dev.to/abh1navv/hexagonal-architecture-3ocl”