TL;DR: This article will explain what a domain gateway is, how to build
one, and why do you want it.

Что такое доменный шлюз?

Шлюз домена — это частный случай шаблона api gateway. Репозиторий java-design-patterns ссылается на gateway pattern как:

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

Шаблон шлюза API предназначен для решения некоторых из этих проблем. В шаблоне шлюза API дополнительный объект (шлюз API) размещается между клиентом и микрослужбами. Задача шлюза API — объединять вызовы микросервисов. Вместо того, чтобы клиент вызывал каждую микрослужбу по отдельности, клиент вызывает шлюз API один раз. Затем шлюз API вызывает все необходимые клиенту микрослужбы.

Так что же такое доменный шлюз? Шлюз домена, как и шлюз API, — это фасад для ваших клиентов. Более того, он позволяет объединять вызовы к серверной части в один вызов для клиентов. И последнее, но не менее важное: это позволяет заменять службы в бэкэнде без ведома наших клиентов.

На следующем рисунке показан возможный пример шаблона шлюза домена:

Если вы больше любите диаграммы классов, возможно, эта иллюстрация будет иметь для вас больше смысла:

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

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

Do’s

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

Не

  • Он не должен обрабатывать какую-либо бизнес-логику. Например, отправка электронных писем, создание файлов и т. д.
  • Он не должен хранить в базе данных какую-либо бизнес-логику или объектные модели. Этот сервис должен быть полностью безгражданным и не знать бизнес-логики.

Хорошо, я убежден…

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

Проблема, представленная в моей предыдущей статье, заключается в следующем: как я могу сгенерировать сразу много спецификаций?

Как мне это построить?

Технический стек

Мы будем использовать следующий технический стек:

Спецификации API

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

Если мы посмотрим на сервисную структуру нашего домена, она будет выглядеть примерно так:

Теперь давайте определим наши характеристики.

Привет Сервис

Наш Hello API будет предоставлять единую конечную точку: /hello/{name}. Эта конечная точка ответит клиенту Hello <NAME> (например, Hello Yonatan).

До свидания Сервис

Наш Goodbye API точно такой же, как Hello API. он будет содержать одну конечную точку: /goodbye/{name}. Эта конечная точка, как и Hello API, ответит клиенту Goodbye <NAME> (например, Goodbye Yonatan).

Служба приветствия

Приветственный API — это фасад двух вышеперечисленных сервисов. Он должен включать 2 конечные точки API:

  • /hello/{name}
  • /goodbye/{name}

Мы найдем все вышеуказанные спецификации в нашем каталоге /resource/api со следующими именами:

  • hello-api.yaml
  • goodbye-api.yaml
  • gateway-api.yaml

Создание нескольких спецификаций

Мы будем повторно использовать ту же настройку, что и в предыдущей статье. Но мы хотели бы изменить его, чтобы мы могли генерировать неограниченное количество спецификаций. Если вам не хватает объяснения того, как настроить плагин OpenAPI Gradle, перейдите и прочитайте мою предыдущую статью.

Первый шаг в нашем путешествии — введение нового класса в наш скрипт сборки Gradle. Этот класс будет содержать всю необходимую информацию о спецификации.

Теперь мы создадим список всех спецификаций, которые мы хотели бы сгенерировать в нашем build.gradle.kts:

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

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

Следующие шаги являются наиболее важными частями. Мы определим некоторые общие функции, которые будут делать следующее:

  • зарегистрируйте новую задачу для каждой спецификации, чтобы сгенерировать код.
  • добавить сгенерированный код в исходный набор.
  • сделать задачу clean завершенной задачей генерации спецификации.
  • make compileKotlin зависит от задачи генерации спецификации.

Давайте начнем.

Мы начнем с создания задач для каждой из наших спецификаций в группе openapi tools:

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

Примечание. мы включаем ApiClient.kt, OAuth.kt, и OAuthOkHttpClient.kt, так как в настоящее время в генераторе есть ошибка. Эта ошибка не позволяет генерировать клиенты Retrofit с помощью Jackson в качестве библиотеки сериализации. Я открыл запрос на включение исправления в репозиторий генератора OpenApi. Если запрос на вытягивание был объединен, вы можете удалить часть exclude из своего проекта.

Последним шагом будет убедиться, что clean задач и compileKotlin хорошо работают без новых задач. Мы создадим новую задачу с именем cleanGeneratedCodeTask, которая будет просто удалять весь сгенерированный код всякий раз, когда мы запускаем задачу clean. Более того, мы хотим убедиться, что до того, как мы создадим наш код, все задачи генерации кода были выполнены.

Как видите, теперь все наши задачи доступны для использования в Gradle!

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

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

Контроллер доменного шлюза

Реализация контроллера шлюза домена очень проста. Это Spring @RestController, который реализует заданный интерфейс. Таким образом, наш код будет выглядеть так:

Клиенты

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

мы начнем с определения нашего клиента OkHttp. Обратите внимание, что я устанавливаю уровень перехватчика регистратора на BODY. Он отлично подходит для отладки, но НИКОГДА не используйте его в рабочей среде. Он будет регистрировать все ваши тела запросов и ответов. Это может привести к раскрытию конфиденциальной информации в ваших журналах. Если вы все еще хотите зарегистрировать тело запроса/ответа. Вам нужно создать собственный перехватчик.

Следующим шагом является определение фабрики преобразователей Джексона. Фабрике нужен ObjectMapper контекст Spring. Не создавайте для него новый ObjectMapper! Я потратил около половины своего дня на работе, пытаясь отладить эту проблему.

Далее мы определим bean-компоненты для наших двух клиентов API. Имейте в виду, что пока мы используем преобразователь, описанный выше, мы вводим общий файл Factory. Мы делаем это для того, чтобы нам было легче заменить библиотеку будущего.

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

Заключение

В этой статье я объяснил преимущества объединения ваших сервисов в доменный шлюз. Мы использовали мощь OpenAPI, чтобы обеспечить надежность нашего API как публично (для наших клиентов), так и внутри (между нашими сервисами).

Все примеры кода в этой статье доступны в моей учетной записи GitHub в этом репозитории: https://github.com/yonatankarp/domain-gateway-demo

Больше информации

Первоначально опубликовано на https://yonatankarp.com.