Переход от монолита к микросервисам на практике

На тему перехода от монолитной к микросервисной архитектуре были написаны замечательные статьи, например, эта, вероятно, один из лучших примеров. Преимущества и недостатки этих архитектур должны быть достаточно ясны. Однако я хочу поговорить о другом: о стратегии. Мы создаем монолиты, потому что с ними легче начинать. Микросервисы обычно возникают в силу необходимости, когда наша система уже находится в производстве.

Однако при принятии решения о переносе возникает множество вопросов — как определить границы сервиса? Как проверить свойства самовосстановления архитектуры микросервиса?

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

Решение

Я твердо придерживаюсь мнения «начинать с монолита», это должен быть модульный монолит, чтобы мы могли легко его разрушить. Существует миф о монолите как о «блоке взаимосвязанного кода». Это далеко не так, большинство монолитов используют возможности современных языков программирования (например, пакеты, модули и т.д.) для разделения различных частей. Обращения между различными частями модульного монолита происходят через четко определенные интерфейсы или шины событий. Моя позиция сторонника монолитных приложений, вероятно, обусловлена моим опытом работы с Java, поскольку Java особенно хорошо подходит для создания больших монолитов. Точка, в которой вы разделите кодовую базу, будет радикально отличаться в зависимости от вашей архитектуры, языка, проблемной области и т.д.

Как сделать объективный выбор в этом отношении? Когда наступает идеальное время для перехода на микросервисы?

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

  • Размер команды — по мере роста команды сохранение сплоченности становится сложной задачей. Это то, что мы можем легко определить, проанализировав рост команды. Следите за скоростью принятия сотрудников на работу и другими показателями, такими как время решения проблем. Это, вероятно, лучшие показатели сложности проекта.
  • Взаимозависимость — преимущества микросервисов могут стать помехой, если проект глубоко взаимозависим и не имеет четких разделительных линий. Некоторые проекты по своей природе глубоко взаимозависимы и не имеют четкого разделения частей. Обратите внимание на транзакционную целостность между различными модулями. Такие функции, как управление транзакциями, не могут переноситься между микросервисами. Если у вас есть система, которая должна быть надежно согласованной, например, банковская система, которая должна быть согласованной в любое время, границы транзакции должны находиться в пределах одного сервиса. Именно такие вещи могут сделать процесс миграции особенно сложным.
  • Тестирование — вы не сможете провести такую работу без значительного количества тестов для конкретных модулей и большого набора интеграционных тестов. Просмотр кода тестов скажет вам больше о вашей готовности, чем любой другой способ. Можете ли вы логически протестировать модуль в изоляции?

Как только вы получите представление о них, вы сможете начать оценивать выгоду, которую вы можете получить от перехода от монолита к микросервисам.

С чего начать?

Предполагая, что монолитный код уже относительно модульный и поддерживает SSO (Single Sign On), мы можем выбрать любой модуль. Как узнать, какой из них принесет наибольшую отдачу от вложенного времени и усилий?

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

  • Посмотрите на трекер проблем/контроль версий — какой модуль наиболее подвержен сбоям?
  • Проверьте модульность — какой модуль самый маленький и наименее взаимозависимый? Можно ли чисто разделить данные? — Лучше всего начинать с более низко висящих фруктов.
  • Составьте профиль приложения — какой модуль является самым дорогим и может выиграть от масштабирования?

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

Избегание крошечной монолитной архитектуры

Люди часто декламируют принципы микросервисов, но продолжают строить то, что не соответствует общим правилам. «Самоисцеление» — самый вопиющий пример. Разъединить части монолита в микросервисное приложение очень сложно. Нам нужно изолировать части и убедиться, что все функционирует достаточно хорошо при масштабировании. Или, что еще хуже, во время простоя.

Как система может выжить, когда развертываемый сервис не работает? Как мы можем протестировать что-то подобное?

Одной из самых больших проблем в такой архитектуре является масштаб развертывания. Мы оборачиваем отдельные сервисы в систему обнаружения и API-шлюзы и выключатели, чтобы включить лечебные свойства. Часто API-шлюзы (и подобные сервисы) — это решения на базе SaaS, но даже если мы сами их развертываем, точно воспроизвести наше производство сложно. Типичные сложности включают в себя кодированные URL-адреса в шлюзах и в самом коде. Случайный обход шлюза и прямой доступ к серверам или базовой инфраструктуре. Это тонкие вещи, которые трудно обнаружить в устаревшем коде и больших системах.

Из-за такой сложной топологии правильное тестирование поведения исцеления практически невозможно при локальной работе. Любой полученный результат будет неточным из-за сильно отличающейся логистики развертывания.

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

Это одно из огромных преимуществ архитектуры микросервисов. В код обнаружения мы можем добавить специальный случай, который предоставляет «фиктивный» или неработающий микросервис для конкретного пользователя. Проблема в том, что симптомы может быть трудно проверить, поскольку «самовосстанавливающийся» сервис будет выглядеть так, как будто он функционирует. В этом случае мы можем использовать журналы или снимки для проверки того, что достигнут правильный код и модуль действительно отключен.

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

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

Полоскание — повторение

Этот процесс наиболее сложен, когда мы отделяем первый микросервис от монолитного приложения. По мере того как мы отделяем все новые и новые части, обычно становится все проще, пока не останется весь монолит. Но на этом пути нас ждут трудности. Изначально мы выбираем достижимую цель, которую легче достичь. По мере продвижения вперед мы сталкиваемся с более сложными проблемами и должны определить границы для службы, которые могут быть не совсем идеальными.

Проблема в том, что часто нам приходится делать эти шаги на основе интуиции. Но когда мы создавали модули, мы могли использовать логическое разделение вместо взаимозависимости. В результате два модуля могут иметь глубокие зависимости и не иметь смысла как микросервис. Разделение их в другом месте или даже объединение их вместе может иметь больше смысла. В качестве примера можно привести бухгалтерскую систему, которая управляет несколькими счетами. Логичное разделение могло бы перенести код, который переводит средства между счетами, в отдельный модуль. Но это сильно усложнит работу. В бухгалтерской системе деньги должны поступать с одного счета и перемещаться на другой, они никогда не могут «исчезнуть». Когда мы добавляем деньги на один счет, мы должны вычесть их с другого, и оба действия должны происходить в одной транзакции. Тривиальным обходным решением может быть вычитание и перемещение средств в одном запросе. Однако это не решит общую проблему, поскольку деньги могут быть сняты с одного счета и разделены на несколько счетов. Выполнение этого в нескольких небольших операциях может вызвать побочные эффекты. Это выполнимо, но лично я бы в таком случае сохранил основную логику учета вместе с системой счетов.

Некоторые из этих взаимозависимостей могут быть выведены из кода и рефакторингом устранены. Переход на обмен сообщениями и асинхронные вызовы. Использование службы обмена сообщениями — один из самых эффективных способов развязки. Многие языки и платформы поддерживают модульный барьер между различными частями. Это позволяет нам изолировать целый модуль от остальной части приложения и ограничить взаимодействие узким интерфейсом. Подняв такой барьер, мы можем использовать компилятор и IDE для принудительного ограничения модулей.

Наконец,

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

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

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

Оцените статью
devanswers.ru
Добавить комментарий