Управление общей кодовой базой в микросервисной архитектуре
При большом количестве микросервисов в проекте приходится сталкиваться с тем, что в некоторых из них нужно дублировать один и тот же код, а обнаружив баг в одном месте, искать и исправлять его везде. При этом, если микросервисы поддерживаются разными разработчиками, то каждый будет исправлять баг по-своему и в дальнейшем будет сложнее привести всё к единообразию. Давайте на примере рассмотрим несколько подходов к переиспользованию кода в микросервисах.
Представим, что у нас есть сайт ветеринарной клиники, где можно зарегистрироваться, выбрать доктора и записаться к нему на прием. Проект может содержать множество микросервисов, но мы выделим для рассмотрения следующие:
user-service — отвечает за авторизацию, регистрацию и за работу с пользователями (как врачами, так и владельцами питомцев).
notification-service — отвечает за email рассылку (это могут быть новости, напоминания о приеме и тд.)
gateway — микросервис, который будет агрегировать ответы от внутренних микросервисов (таких, как user-service) и подготавливать ответ для фронтенда.
Категории микросервисов
В нашем примере микросервисы можно разделить на несколько категорий:
микросервисы, которые используют базу данных и не предоставляют никакие API методы. В списке выше это notification-service, который делает рассылки по расписанию или использует какой-нибудь брокер сообщений (типа kafka или rabbitMQ).
микросервисы, которые не используют базу данных, а общаются только со сторонними API и предоставляют свои API методы для взаимодействия. В нашем примере это gateway, который только обрабатывает ответы от других микросервисов.
микросервисы, которые используют и базу данных и предоставляют свои API методы для работы (как, например, user-service).
Что, если мы хотим, чтобы все эти микросервисы следовали каким-то внутренним стандартам? Например, те, которые предоставляют API методы для взаимодействия, должны содержать автодокументацию с описанием всех методов посредством OpenAPI, а логирование во всех микросервисах должно быть настроено так, чтобы все логи отправлялись в splunk. В этом случае нам придется дублировать конфигурации и какие-то утилитные классы для работы с датами, ценами и прочим в каждом микросервисе. Для того, чтобы этого избежать, нам подойдёт своя библиотека, в которой мы будем хранить весь переиспользуемый код. Рассмотрим несколько возможных реализаций данной библиотеки.
Одна библиотека для всех микросервисов
Мы можем создать новый maven проект, в котором мы подключим все зависимости, которые могут быть использованы в микросервисах. В этом случае в каждом микросервисе будет большое количество неиспользуемых зависимостей, что повлечет за собой увеличение размера финального jar файла. Помимо этого придется строго следить за тем, чтобы не создавались новые бины без надобности (зачем нам бин для работы с базой данных, если он не нужна в микросервисе?).
Множество разных общих библиотек
Мы можем разделить нашу библиотеку на множество проектов. Тогда мы избавимся от проблем из предыдущего примера, но столкнемся с новыми. При изменении сразу в нескольких библиотеках нужно будет делать множество пулл реквестов и выпускать множество версий этих библиотек.
Одна общая многомодульная библиотека
В этом случае все изменения в одном пулл реквесте, нет никаких лишних зависимостей и бинов в микросервисах, публиковать можно как отдельные версии подпроектов так и выпускать одну версию. Давайте подробнее рассмотрим, как же реализовать подобную библиотеку с использованием maven.
Для примера, определимся с подмодулями, которые нам могут потребоваться, и что в них будет находиться:
core — в нем мы будем хранить что-то базовое для всех микросервисов. Например, какие-то наши базовые исключения типа PlatformException или ApplicationException. Также там можно хранить какие-то базовые структуры данных, типа Pair, Timer и все подобное, что может использоваться в любом нашем микросервисе вне зависимости от его назначения.
utils — в этом модуле будем хранить утилитные классы общего назначения. Например, классы для работы с датами, ценами, json и тп.
database подмодуль будет содержать какие-то полезные вещи для работы с базой данных. Это могут быть кастомные сериалайзеры / десериалайзеры полей базы данных и многое другое. В нашем примере этот подмодуль могут использовать user-service, notification-service.
logging — в нем мы сможем описать конфигурацию для направления всех наших логов в splunk.
client — в этом подмодуле мы сможем хранить всё, что может потребоваться в микросервисах, которые взаимодействуют с другими API. Его может использовать gateway.
api — здесь мы будем хранить все для сервисов, которые будут предоставлять методы API. Например, конфигурацию для автогенерации документации и тому подобное. Его можно подключить в user-service и gateway.
любые другие подмодули, который вам могут понадобиться.
Описание многомодульного проекта
Давайте начнем с описания базового pom файла. В качестве родителя будем использовать pom файл из предыдущей статьи для того, чтобы подключаемые в подмодулях зависимости имели те же версии что и во всех микросервисах.
4.0.0
com.example
base-pom
0.0.1-SNAPSHOT
com.example.common
common-library-parent
0.0.1-SNAPSHOT
pom
common-library-example-parent
Demo project
core
utils
database
logging
client
api
В каждый подмодуль нужно добавить свой pom файл. В нем можно добавить все зависимости, необходимые в конкретном подмодуле, включая зависимости на другие подмодули. Для примера возьмем pom файл из подмодуля client, который требует зависимость на feign (подробнее о том, как его использовать я писал в своей статье) и исключения из подмодуля core. Таким образом pom файл будет выглядеть следующим образом:
4.0.0
com.example
common-library-parent
0.0.1-SNAPSHOT
client
${project.parent.version}
client
Demo project
${project.groupId}
core
${project.version}
io.github.openfeign
feign-spring4
В подмодуле мы можем добавить классы для расширения возможностей feign клиента. Например, свою реализация логирования запросов / ответов от API и многое другое.
Финальная структура проекта будет выглядеть следующим образом:
Структура проекта
Подключение многомодульной библиотеки в микросервисы
Для того, чтобы использовать данную библиотеку, как и в случае с parent pom, ее сначала нужно опубликовать в локальный репозиторий (или же в другом хранилище, например, nexus) командой mvn install
Вы увидите следующее:
Далее вы уже сможете подключить необходимые подмодули в микросервисы. Подключим подмодуль client в наш микросервис gateway для того, чтобы легко добавить в нем интеграцию с user-service:
0.0.1-SNAPSHOT
com.example
client
${common-lib.version}
Заключение
Конечно, количество подмодулей может быть любым. Все зависит от того, что именно требуется в вашем проекте.