Да, верно, большинство архитектур, доступных сегодня, так или иначе похожи. Прежде чем вы бросите пистолет и прочитаете мне лекцию «ты ничего не знаешь», позвольте мне объяснить, почему я так сказал.

Прежде всего, то, чего в основном пытаются достичь все эти архитектуры, - это разделение проблем. Теперь это восходит к вековым твердым принципам, где принцип единой ответственности предполагает, что каждый должен делать что-то одно и делать это безупречно, чтобы, когда вы необходимо что-то изменить, это ограничено только этим компонентом и не влияет ни на что другое в системе (есть многочисленные преимущества, которые я пропущу, чтобы сэкономить время).

Это касается и современных веб-сервисов. Если вы хотите перейти от REST к gRPC, вам не нужно трогать код доступа к базе данных, если вы добавляете новые правила проверки, вам не нужно менять контроллеры. Чем меньше вещей вам нужно изменить, тем меньше вероятность появления новой ошибки, и если что-то сломается, вы точно знаете, где искать.

Таким образом, со всеми этими архитектурами вы в основном разбиваете свою систему так, чтобы

  1. Ваша основная бизнес-логика не зависит от чего-либо вне ее домена и предоставляет единый DSL для кода потребителя. Следовательно, потребительский код нарушается любыми изменениями, которые вы вносите в свою основную логику, и наоборот.
  2. Ваш основной логический код легко проверить, поскольку он не зависит от какой-либо структуры.
  3. Ваша основная логика не зависит от того, как вы извлекаете данные, поэтому не будет иметь значения, если вы решите использовать DynamoDB вместо MongoDB завтра. Не имеет значения, используете ли вы хранимые процедуры, запросы к базе данных или структуру ORM. Для основной логики доступ к данным становится просто абстракцией.

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

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

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

Несмотря на то, что я заявил, что все архитектуры одинаковы, мы все равно собираемся изучать гексагональную архитектуру, поскольку название статьи требует от меня этого 😝

Гексагональная архитектура пытается изолировать основную логику приложения от инфраструктуры или чисто технического кода. Так что мы можем заменить любую из необходимых нам инфраструктур.

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

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

1. Шестиугольник

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

  1. Домен. Это основные бизнес-объекты, которые инкапсулируют бизнес-данные и бизнес-логику. Они не зависят от какой-либо структуры и выполняют только логические вычисления. Самое главное, что у них нет никакой зависимости.
  2. Вариант использования. Это набор операций, поддерживаемых системой. Мне нравится думать о них как о оркестраторах, которые содержат в основном бизнес-логику приложения. В основном они будут взаимодействовать с доменами, другими вариантами использования и репозиторием для завершения бизнес-операции.
  3. Порты. Это простые интерфейсы, расположенные на границе шестиугольника. Они обеспечивают связь / передачу данных в шестиугольник и из него. Все, что находится за пределами шестиугольника, будет зависеть от порта ввода для вызова любого из методов варианта использования. Точно так же вариант использования должен зависеть от порта вывода для доступа к чему-либо за пределами шестиугольника.

2. Адаптеры

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

Например, у нас может быть HTTP-адаптер, который представляет собой не что иное, как контроллер REST, который будет вызывать входной порт для выполнения бизнес-операции. Точно так же может быть адаптер базы данных, который будет реализовывать выходной порт, а затем вариант использования может вызвать этот выходной порт для передачи данных в базу данных.

Структура зависимости

Вот простая диаграмма, показывающая отношения между различными компонентами шестиугольного приложения.

  1. Адаптер REST зависит от порта ввода, поэтому он может вызывать операции внутри шестиугольника.
  2. Порт ввода - это интерфейс, который устанавливает контракт операций, поддерживаемых шестиугольником или приложением.
  3. Вариант использования реализует порт ввода, следовательно, обеспечивает реализацию всех поддерживаемых операций. Он также зависит от выходного порта, поэтому может вызывать определенные там методы.
  4. Порт вывода также является интерфейсом, который устанавливает контракт для исходящей операции из шестиугольника.
  5. Адаптер базы данных обеспечивает реализацию методов в выходном порту. Это означает, что они фактически обрабатывают весь код, связанный с базой данных.

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

Несмотря на то, что для варианта использования требуются методы базы данных, он зависит только от порта, который находится внутри шестиугольника, а не от какого-либо репозитория / кода DAO. Адаптер реализует этот порт, инвертируя зависимость от адаптера к шестиугольнику. Инверсия зависимостей, yaaayyyyy, замечательные вещи, которые могут делать интерфейсы.

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

Домен, ведущий и объект

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

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

  1. Домен: это все тот же домен, который я объяснил выше. Это кодовое представление реального объекта домена. Например, если ваша система имеет дело с продуктом, у вас должен быть хотя бы один доменный класс с именем Product.
  2. Presenter: это объект, который представляет домен в формате, понятном вашему потребителю, и большую часть времени должен содержать только данные, а не поведение. Например, если вы хотите отправить продукт в подчиненную систему и сделать всю эту операцию идемпотентной, вам необходимо добавить идентификатор запроса. Теперь вы можете создать новый объект, который имеет любой объект домена, имеющий + request-id, и он станет вашим докладчиком. Вашему пользовательскому интерфейсу могут потребоваться данные в формате GeoJSON, но для некоторого другого восходящего API могут потребоваться данные в формате WKT, вам снова понадобятся два разных докладчика, чтобы представить ваш ответ.
  3. Сущность: Сущности - это то, что я называю представлением ваших данных в базе данных. Я имею в виду, что вы можете представлять одни и те же данные в базе данных по-разному, один может быть более оптимальным, чем другие, для запросов к базе данных. Поэтому мы создаем другой класс, который напоминал бы таблицу (для БД SQL) или документ (для БД документов), чтобы наши операции с базой данных были оптимизированы.

Вы видели, что я там делал? Еще разделение опасений. Ваш объект домена заботится обо всех бизнес-правилах, ваш докладчик заботится о потребностях вышестоящей системы, ваша сущность заботится о потребностях базы данных.

Итак, смешав это с нашей гексагональной структурой, вот как выглядит поток данных.

Все просто, правда? Не волнуйтесь, если вы не полностью понимаете этот пример кода.

Это были все концепции, теперь мы можем перейти к коду. Еще один момент. В следующем разделе я напишу код на Java + Spring WebFlux. Не волнуйтесь, если вы не знаете ни Java, ни Spring, реализация будет чрезвычайно упрощенной и простой в использовании.

Hexagon на Spring WebFlux

Мы собираемся создать очень простое приложение REST для управления «магазином». Мы должны иметь возможность хранить и извлекать данные и выполнять различные запросы. Окончательное решение можно найти здесь.



Допустим, требования к нашей текущей задаче есть.

  1. Вы должны иметь возможность сохранить магазин в БД.
  2. Мы также должны вести подсчет количества магазинов с определенным почтовым индексом для будущих операций. Следовательно, когда мы сохраняем магазин в БД, мы увеличиваем счетчик.

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

Строим шестиугольник

Вот так мой шестиугольник будет выглядеть после завершения

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

Входной адаптер

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

Не расстраивайтесь, увидев Mono, это издатель реактивного потока, который может опубликовать не более 1 события. И это событие будет содержать простые данные. Необязательно разбираться в реактивном программировании, чтобы следовать за ним. Вы можете узнать больше о реактивном программировании на Java здесь https://projectreactor.io/

Домен

Эта базовая модель предметной области Магазина

У него также есть субдомен Location, который выглядит так

Мы пытаемся моделировать нашу предметную область как можно ближе к физическим объектам для лучшей читаемости и простоты.

Услуга

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

Он проверяет, действителен ли объект домена, если да, то вносит некоторые изменения в базу данных, иначе выдает ошибку.

Пара вещей, на которые стоит обратить внимание

  1. Реализация варианта использования использует разные порты для взаимодействия с базой данных.
  2. Он делегировал задачу проверки объекту домена. (Класс Store на самом деле является классом домена). Поскольку проверка - это бизнес-операция, которую должен выполнять домен.

Выходные порты

У нас есть два выходных порта. Один для вставки магазина, другой для увеличения количества магазинов. Они выглядят так

На этом все, что находится внутри шестиугольника, завершено, пора переходить к переходникам.

Строительные адаптеры

Так будет выглядеть конструкция переходников в конце

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

Это наш адаптер базы данных

Несколько вещей, на которые стоит обратить внимание

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

Репозитории

Репозитории - это простые MongoRepositories, которые выглядят так

Сущности

Так выглядит сущность и GeoJSONPoint подобъект.

Если вы заметили, представление координат в домене и сущности отличается. Это потому, что для выполнения geospatial запросов в MongoDB вам необходимо иметь ваши геоданные в geojson формате, и мы, возможно, захотим поддерживать геопространственные запросы в будущих спринтах. Таким образом, мы можем очень легко моделировать нашу сущность, чтобы поддерживать это, не меняя модель предметной области. Оцените преимущества разделения домена, сущности и докладчика 👯

Веб-адаптер

У нас в веб-адаптере есть два класса.

  1. Маршрутизатор, который направляет веб-запросы обработчику.
  2. Обработчик, который вызывает вариант использования для выполнения бизнес-операций.

Это функциональный стиль написания веб-обработчиков вместо Контроллеров, как мы обычно делаем.

Если вы заметили, обработчик имеет дело только с объектом Presenter.

Ведущий

Наш докладчик довольно плоский, потому что пользовательскому интерфейсу нужны данные именно таким образом.

То, как одни и те же данные представлены в презентаторе, домене и сущности, совершенно разные и оптимизированы для выполняемых ими операций или их использования, удивительно, правда?

Если вам интересно, почему я не использовал Lombok для генерации геттеров для домена, причина в том, что время от времени геттеры в домене будут иметь некоторую логику в них. Поэтому я предпочел писать ванильные Java-геттеры вместо ломбока. Но вы всегда можете использовать ломбок для простых геттеров.

И это в основном завершает поставленную задачу, друзья.

И теперь все дерево пакетов выглядит так

Если вам не удалось соединить все точки, вы можете клонировать это репо и запустить его локально, чтобы увидеть весь поток.



Но

Все сказано и сделано, эти концепции и архитектуры чрезвычайно эффективны и имеют множество преимуществ. Но если вы разрабатываете простой CRUD API, все это, вероятно, станет для вас излишним и лишним. Если вы разрабатываете сложный API, вам нужно поддерживать его дольше и требуется гибкость, тогда использование этих стилей имеет смысл.

Выводы

  1. Используйте стиль архитектуры, который удобен вашей команде. Потому что легко представить новую архитектуру, но сложнее всего следовать ей неукоснительно.
  2. Мы поняли концепции гексагональной архитектуры.
  3. Мы поняли преимущества разделения домена, докладчика и сущности.
  4. Мы также разработали полнофункциональный REST API, используя все эти концепции в Spring WebFlux.

Спасибо за прочтение!

Я Аритра Дас, я работаю разработчиком программного обеспечения и мне очень нравится создавать распределенные системы. Не стесняйтесь обращаться ко мне в Linkedin или Twitter по любым вопросам, связанным с технологиями.

Удачного обучения…