Разработка микросервисного приложения: принципы, шаблоны и практика
Разработка микросервисного приложения — это построение системы из независимых сервисов, каждый из которых отвечает за свою бизнес-задачу. Части общаются через программные интерфейсы, обновляются отдельно и масштабируются по необходимости. В статье разобраны границы сервисов, способы взаимодействия, работа с данными, проверенные шаблоны, контейнеризация и мониторинг.
Содержание
Материал разбит на смысловые блоки. Каждый отвечает на отдельный вопрос по теме.
- Что такое микросервисная архитектура
- Монолит и микросервисы: сравнение подходов
- Когда микросервисы оправданы, а когда вредны
- Как определить границы сервиса
- Синхронное взаимодействие между сервисами
- Асинхронное взаимодействие через события
- Шлюз и единая точка входа
- Обнаружение сервисов и маршрутизация
- Работа с данными: своя база у каждого сервиса
- Распределенные транзакции и согласованность
- Основные шаблоны проектирования
- Отказоустойчивость и защита от каскадных сбоев
- Контейнеризация и оркестрация
- Развертывание и автоматизация выпуска
- Мониторинг, логи и трассировка запросов
- Безопасность микросервисной системы
- Тестирование распределенного приложения
- Технологии и стек разработки
- Переход с монолита на микросервисы
- Частые ошибки при внедрении
Дальше каждый пункт разобран подробно. Материал поможет принять решение об архитектуре и избежать типовых проблем.
Что такое микросервисная архитектура
Микросервисная архитектура делит приложение на набор небольших независимых сервисов. Каждый решает одну бизнес-задачу и работает как отдельная программа.
Сервисы запускаются самостоятельно, имеют собственный процесс и общаются через сеть. Прямых вызовов функций между ними нет.
Основные признаки такой системы:
- Одна ответственность. Сервис отвечает за конкретную предметную область: заказы, оплата, уведомления.
- Собственные данные. У каждого сервиса своя база, доступ к чужой напрямую запрещен.
- Независимое развертывание. Обновление одной части не требует остановки остальных.
- Свобода технологий. Разные сервисы могут использовать разные языки и хранилища.
- Отказоустойчивость. Сбой одного компонента не останавливает всю систему.
Важно! Микросервисы решают организационные проблемы больших команд, а не технические проблемы кода. Для небольшого проекта они добавляют сложность без выгоды.
Монолит и микросервисы: сравнение подходов
Оба подхода имеют право на существование. Выбор определяется размером проекта и командой.
Сравнение по ключевым критериям:
| Критерий | Монолит | Микросервисы |
| Структура | Единое приложение со всеми функциями | Набор независимых сервисов |
| Развертывание | Обновляется целиком | Каждый сервис выпускается отдельно |
| Масштабирование | Копируется приложение полностью | Масштабируется только нагруженная часть |
| Порог входа | Низкий, разработка начинается сразу | Высокий, нужна зрелая инфраструктура |
| Отладка | Проще, все в одном процессе | Сложнее, запрос проходит через несколько сервисов |
| Скорость команд | Замедляется при росте числа разработчиков | Команды работают параллельно и независимо |
| Требования к эксплуатации | Один сервер и одно приложение | Оркестрация, мониторинг, автоматизация выпуска |
Монолит выигрывает на старте. Он проще в разработке, отладке и запуске.
Микросервисы выигрывают на масштабе. Когда над проектом работают несколько команд, независимые релизы дают серьезное преимущество.
Когда микросервисы оправданы, а когда вредны
Решение принимают по конкретным признакам, а не по популярности подхода.
Микросервисы имеют смысл, если выполняются условия:
- Несколько команд. Разработчики мешают друг другу в одной кодовой базе.
- Разная нагрузка на части системы. Один модуль требует в десять раз больше ресурсов, чем остальные.
- Разные требования к обновлению. Одну часть меняют ежедневно, другую — раз в квартал.
- Зрелая инфраструктура. В компании уже есть автоматизация выпуска и мониторинг.
- Понятные границы предметной области. Ясно, где заканчивается один блок и начинается другой.
Подход вредит в противоположной ситуации. Небольшой проект с одной командой получает распределенную систему без выигрыша.
Разумная стратегия — начать с модульного монолита. Код делят на четкие части внутри одного приложения, чтобы позже выделить их без переписывания.
Как определить границы сервиса
Это самая сложная задача проектирования. Неверное разделение хуже монолита.
Правильная граница проходит по бизнес-функции, а не по техническому слою. Сервис заказов, сервис оплаты и сервис уведомлений — верный подход. Сервис базы данных и сервис интерфейса — неверный.
Признаки удачно выделенного сервиса:
- Понятная зона ответственности. Название описывает бизнес-задачу одним словом.
- Минимум обращений наружу. Большинство операций выполняется внутри сервиса.
- Своя модель данных. Сущности принадлежат сервису и не используются другими напрямую.
- Самостоятельная ценность. Сервис можно описать без упоминания соседних.
- Отдельная команда. Один коллектив полностью владеет разработкой и эксплуатацией.
Слишком мелкое дробление опасно. Десяток сервисов, которые всегда вызываются вместе, стоило оставить одним.
Важно! Если для выполнения одной операции нужно обратиться к пяти сервисам, границы проведены неправильно. Такая цепочка медленна и ломается при отказе любого звена.
Синхронное взаимодействие между сервисами
Сервисы общаются через сеть. Первый способ — прямой запрос с ожиданием ответа.
Распространенные протоколы:
| Протокол | Формат данных | Особенности |
| REST поверх HTTP | JSON в текстовом виде | Простота, читаемость, поддержка всеми инструментами |
| gRPC | Двоичный формат по описанию схемы | Высокая скорость, строгий контракт, потоковая передача |
| GraphQL | Запрос с указанием нужных полей | Гибкость для клиента, экономия трафика |
REST остается основным выбором для внешних интерфейсов. Его понимают все инструменты и легко отлаживать вручную.
gRPC чаще применяют внутри системы. Двоичный формат работает быстрее текстового, а схема гарантирует совместимость версий.
Синхронный вызов имеет очевидный недостаток. Пока один сервис ждет ответа другого, он занят и не обслуживает другие запросы.
Асинхронное взаимодействие через события
Второй способ снимает жесткую связь между сервисами. Отправитель не ждет ответа.
Сервис публикует сообщение о произошедшем событии. Заинтересованные компоненты получают его и выполняют свою часть работы.
Посредником выступает брокер сообщений. Он принимает сообщения, хранит их и доставляет получателям.
Преимущества такого подхода:
- Независимость. Отправитель не знает, кто обработает сообщение и когда.
- Устойчивость к сбоям. При недоступности получателя сообщение ждет в очереди.
- Сглаживание нагрузки. Всплеск запросов накапливается вместо отказа системы.
- Простота расширения. Новый подписчик добавляется без изменения отправителя.
Плата за это — сложность отслеживания. Понять, что происходит после публикации события, труднее, чем проследить прямой вызов.
Шлюз и единая точка входа
Клиенту неудобно знать адреса десятка сервисов. Эту задачу решает шлюз.
Он принимает все внешние запросы и направляет их нужным сервисам. Мобильное приложение обращается к одному адресу.
Дополнительные функции шлюза включают проверку авторизации, ограничение частоты запросов, кеширование ответов, ведение журнала обращений и объединение данных из нескольких сервисов.
Обратная сторона — риск узкого места. Шлюз запускают в нескольких копиях, иначе он становится единственной точкой отказа.
Обнаружение сервисов и маршрутизация
В динамической системе адреса сервисов постоянно меняются. Копии запускаются и останавливаются автоматически.
Прописывать адреса в настройках невозможно. Нужен механизм, который знает актуальное расположение компонентов.
Реестр сервисов решает эту задачу. Каждый экземпляр при запуске регистрируется, а при остановке исключается из списка.
Запрашивающая сторона обращается к реестру и получает адрес живого экземпляра. В системах оркестрации это работает автоматически через внутренние имена.
Работа с данными: своя база у каждого сервиса
Это одно из главных правил подхода и самое сложное для соблюдения.
Каждый сервис владеет своими данными. Другие компоненты обращаются к ним только через программный интерфейс, а не напрямую к таблицам.
Причина проста. Общая база возвращает систему к монолиту: изменение схемы затрагивает все сервисы сразу, и независимость исчезает.
Плата за независимость — дублирование данных. Сервису доставки нужен адрес клиента, и он хранит копию нужных полей у себя.
Копии синхронизируют через события. При изменении адреса сервис клиентов публикует сообщение, остальные обновляют свои данные.
Важно! Совместные запросы к нескольким таблицам перестают работать. Данные из разных сервисов собирают в коде или готовят заранее в отдельном представлении.
Распределенные транзакции и согласованность
В монолите операция с несколькими таблицами выполняется одной транзакцией. Либо все изменения применяются, либо ни одно.
В распределенной системе такой механизм недоступен. Данные лежат в разных базах на разных серверах.
Вместо строгой согласованности применяют согласованность в конечном счете. Система приходит в непротиворечивое состояние через некоторое время после операции.
Если один шаг не проходит, выполняются компенсирующие действия. Оплата возвращается, резерв снимается, заказ отменяется.
Такой сценарий описывают отдельным шаблоном, который координирует последовательность шагов и откатов.
Основные шаблоны проектирования
Практика выработала набор проверенных решений типовых задач.
Наиболее востребованные шаблоны:
| Шаблон | Какую задачу решает |
| Сага | Последовательность операций в разных сервисах с компенсацией при сбое |
| Разделение команд и запросов | Разные модели для записи и для чтения данных |
| Хранение событий | Состояние восстанавливается из журнала произошедших изменений |
| Предохранитель | Прекращение обращений к отказавшему сервису на время восстановления |
| Переборки | Изоляция ресурсов, чтобы сбой одной части не исчерпал общий пул |
| Внешний помощник | Вынос общих задач в отдельный контейнер рядом с сервисом |
| Душитель | Постепенная замена монолита сервисами без остановки работы |
Шаблон саги существует в двух вариантах. В первом сервисы реагируют на события друг друга самостоятельно, во втором последовательностью управляет отдельный координатор.
Координируемый вариант проще отлаживать. Логика процесса собрана в одном месте, а не размазана по подписчикам.
Отказоустойчивость и защита от каскадных сбоев
В распределенной системе отказы неизбежны. Сеть теряет пакеты, сервисы перезапускаются, базы становятся недоступны.
Опасен не отказ сам по себе, а его распространение. Медленный сервис задерживает вызывающих, они накапливают ожидающие запросы и тоже перестают отвечать.
Приемы защиты:
- Таймауты. Ожидание ответа ограничивают, а не ждут бесконечно.
- Предохранитель. После серии неудач обращения к сервису временно прекращаются.
- Повторы с задержкой. Запрос повторяют через увеличивающиеся интервалы, а не мгновенно.
- Изоляция ресурсов. Отдельные пулы подключений для разных сервисов.
- Ответ по умолчанию. При недоступности компонента возвращают упрощенный результат.
Контейнеризация и оркестрация
Микросервисы почти всегда разворачивают в контейнерах. Приложение упаковывается со всеми зависимостями в единый образ.
Образ запускается одинаково на машине разработчика и на рабочем сервере. Проблема несовпадения окружений исчезает.
Управлять десятками контейнеров вручную невозможно. Эту работу берут на себя системы оркестрации.
Они решают несколько задач: размещение контейнеров по серверам, перезапуск упавших экземпляров, распределение нагрузки, обновление без остановки и автоматическое масштабирование по метрикам.
Дополнительный слой — сервисная сетка. Она берет на себя шифрование трафика между сервисами, повторы, таймауты и сбор метрик без изменения кода приложения.
Развертывание и автоматизация выпуска
Ручное обновление десятка сервисов нереально. Автоматизация становится обязательной.
Типовая цепочка выпуска включает сборку образа, прогон автоматических тестов, публикацию в хранилище образов и развертывание в нужном окружении.
Каждый сервис имеет собственную цепочку. Это и есть главное преимущество подхода — независимые релизы.
Обновление выполняют без простоя. Новые экземпляры поднимаются рядом со старыми, трафик переключается после проверки готовности.
Мониторинг, логи и трассировка запросов
В распределенной системе понять причину проблемы сложнее. Один пользовательский запрос проходит через несколько сервисов.
Наблюдаемость строится на трех уровнях:
- Метрики. Числовые показатели: время ответа, количество запросов, доля ошибок, загрузка ресурсов.
- Логи. Текстовые записи о событиях, собранные централизованно из всех сервисов.
- Трассировка. Полный путь запроса через систему с временем на каждом участке.
Трассировка особенно важна. Она показывает, какой именно сервис в цепочке замедляет ответ.
Работает это через сквозной идентификатор. Запрос получает уникальный номер на входе, и все сервисы передают его дальше.
Логи собирают в единое хранилище. Искать записи на конкретном сервере при десятках экземпляров невозможно.
Безопасность микросервисной системы
Распределенная архитектура увеличивает количество точек, требующих защиты.
Внешний периметр закрывают на шлюзе. Он проверяет токен пользователя и отклоняет неавторизованные обращения.
Внутренний трафик тоже защищают. Сервисы подтверждают свою подлинность друг перед другом, а соединения шифруются.
Секреты хранят в специализированном хранилище. Пароли к базам и ключи внешних сервисов не должны лежать в настройках контейнера.
Тестирование распределенного приложения
Проверка микросервисной системы устроена сложнее, чем у монолита.
Уровни тестирования:
- Модульные тесты. Проверка отдельных функций внутри сервиса, самый быстрый уровень.
- Тесты сервиса. Проверка его программного интерфейса с подменой внешних зависимостей.
- Контрактные тесты. Гарантия, что сервис отдает данные в формате, ожидаемом потребителями.
- Интеграционные тесты. Совместная работа нескольких компонентов в реальном окружении.
- Сквозные тесты. Полный пользовательский сценарий через всю систему.
Контрактные тесты особенно полезны. Они ловят нарушение совместимости до выкатки в рабочую среду.
Сквозных тестов делают немного. Они медленные, нестабильные и сложны в поддержке.
Технологии и стек разработки
Свобода выбора технологий — заявленное преимущество подхода. На практике ее ограничивают разумными рамками.
Распространенные языки для микросервисов включают Java с готовой экосистемой инструментов, Go с быстрым запуском и малым потреблением памяти, Python для сервисов работы с данными и Node.js для интерфейсных задач.
Полный зоопарк технологий вредит. Каждый новый язык требует своих инструментов сборки, мониторинга и специалистов для поддержки.
Разумный подход — два-три утвержденных стека. Команда выбирает подходящий из списка, а не любой понравившийся.
Переход с монолита на микросервисы
Переписывать работающую систему с нуля почти всегда ошибка. Проект растягивается на годы и не приносит ценности.
Рабочий способ — постепенное вытеснение. Новая функциональность реализуется отдельными сервисами, а существующая переносится по частям.
Порядок перехода:
- Навести порядок в монолите. Разделить код на модули с четкими границами.
- Поставить шлюз перед системой. Он направляет запросы либо в монолит, либо в новые сервисы.
- Выделить первый сервис. Начинают с наименее связанной части: уведомления, отчеты, поиск.
- Разделить данные. Перенести таблицы выделенной области в отдельную базу.
- Переключить трафик. Направить обращения на новый сервис и убедиться в стабильности.
- Повторить для следующей области. Постепенно уменьшать монолит.
Частые ошибки при внедрении
Проекты спотыкаются на повторяющихся проблемах.
Типовые промахи:
- Микросервисы на старте проекта. Небольшая команда получает сложность без выигрыша.
- Общая база данных. Формально сервисы разделены, фактически связаны намертво.
- Слишком мелкое дробление. Десяток сервисов на одну операцию замедляет систему.
- Разделение по техническим слоям. Сервис интерфейса и сервис базы вместо бизнес-областей.
- Отсутствие трассировки. Причину замедления невозможно найти без сквозного отслеживания запроса.
- Синхронные цепочки вызовов. Отказ последнего звена обрушивает всю операцию.
- Игнорирование инфраструктуры. Автоматизацию выпуска и мониторинг откладывают на потом.
Большинство проблем предотвращается на этапе проектирования. Переделка границ сервисов в работающей системе обходится дорого.
- Содержание
- Что такое микросервисная архитектура
- Монолит и микросервисы: сравнение подходов
- Когда микросервисы оправданы, а когда вредны
- Как определить границы сервиса
- Синхронное взаимодействие между сервисами
- Асинхронное взаимодействие через события
- Шлюз и единая точка входа
- Обнаружение сервисов и маршрутизация
- Работа с данными: своя база у каждого сервиса
- Распределенные транзакции и согласованность
- Основные шаблоны проектирования
- Отказоустойчивость и защита от каскадных сбоев
- Контейнеризация и оркестрация
- Развертывание и автоматизация выпуска
- Мониторинг, логи и трассировка запросов
- Безопасность микросервисной системы
- Тестирование распределенного приложения
- Технологии и стек разработки
- Переход с монолита на микросервисы
- Частые ошибки при внедрении