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


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


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


    Сначала монолит


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


    От микросервисов не уйти


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


    Главное начать


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


    Вышеупомянутые три утверждения ставят перед нами два вопроса: когда и как. Давайте по порядку.


    Как понять, что наступил тот самый момент, что пора уже пилить микросервисы?


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


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


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


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


    С чего начать миграцию архитектуры в пользу микросервисов?


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


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


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


    Вместо выводов.


    Микросервисы — хорошо. Монолит — хорошо. Хороший код — хорошо. Рабочий код — хорошо. Статья не призывает срочно менять принцип разработки в приложениях, которые тормозят, криво написаны или написаны не так, как хотелось бы. Так же микросервисная архитектура не является панацеей от всех бед разработчиков. В статье описываются точки и способы перехода от монолитной к микросервисной архитектуре.


    "Отказоустойчивость", "распределенность", "масштабируемость" это безусловно хорошо, но не это главное преимущество микросервисной архитектуры. Самое важное, что дает такой подход — удешевление стоимости внесения изменений.

    Поделиться публикацией
    Комментарии 30
      +1
      Ну смысла в этой статье нет уже книгу написали.
      Не всегда микрокосервисы лучше монолита. не говорите мне про масштабируемый код в вакууме )
        +20
        При переходе на микросервисы надо учитывать общую стоимость владения, а не только затраты на разработку. Разработка микросервисного приложения и правда может оказаться дешевле. Но жизнь продукта разработкой не заканчивается, далее следует саппорт, затраты на который обычно в разы превышают затраты на разработку. По разным оценкам стоимость разработки составляет примерно 10-20%, в то время как саппорт — 60-80% в общих затратах в течение периода эксплуатации.

        Так вот в случае микросервисной архитектуры затраты на саппорт минимум удваиваются.

        Процитирую свой коммент к другой статье, почему это происходит
        В моей компании тоже все на микросервисах, только счастья это не приносит:

        • если ищу причину какого-то бага, то в большинстве случаев где-то по ходу анализа исходника приложения натыкаешься на вызов очередного endpoint-а, приходится скачивать его исходники тоже, а там опять endpoint куда-то еще, и т.д. О локальном дебаге речь вообще даже не поднимается, так как слишком много приложений-микросервисов придется локально конфигурировать. В итоге баги ищутся, в основном, путем смотрения на код.

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

        • особенно странно видеть, как зоопарк микросервисов на разных версиях разных языков оперирует с одними и теми же логическими связанными данными. Я, конечно, понимаю, что хотели делать масштабируемо, но на уровне данных все SQL-запросы в итоге идут к одним и тем же базам данных, от чего БД становятся узким местом. Но отмасштабировать БД, как правило, намного труднее.

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

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

        • микросервисы, когда вызывают другие микросервисы по HTTP, работают существенно медленнее, чем если бы тот же код исполнялся из одного процесса, что часто приводит к необходимости все переделывать без микросервисов

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

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

        В общем это не факт, что на микросервисах удасться сэкономить.
          0
          Почему микро-сервис — это обязательно отдельный проект? Ведь можно иметь единую кодовую базу и при этом с успехом использовать микро-сервисы. Например, монолитный код проекта можно запускать с разными конфигами, таким образом формируя любое необходимое количество микро-сервисов.
            +2
            Ключевое слово «можно». Любой проект, особенно, разбитый на атомарные независимые модули, развивается неравномерно. Некоторые части, стабильно работающие и не требующие дополнения функционала, могут прожить нетронутыми несколько лет. За это время многое может произойти: схлопывание delphi и FoxPro, выход нескольких новых стандартов ЯП, релиз очередной Windows и т.д. и т.п. В итоге, микросервис, являвшийся подпроектом, устаревает. Проблемы совместимости в нём чинят методом «лишь бы работало», так как есть куда более важные задачи, нежели переписывание РАБОЧЕГО кода.

            И, заметьте, я ни слова ещё не написал про то, что некоторые задачи гораздо проще решать, используя разные ЯП.
              0
              Описанный вами подход используется, но это не про микросервисы.
              Почитайте здесь: http://microservices.io/articles/scalecube.html
                0
                Только это будет не микросервис, а распределенный монолит.
                +3
                В цитате из спойлера половина проблем от отсутствия нормального деплоя. Естественно, что и локально, и удаленно такую кучу сервисов поднять нереально, просто в лоб используя обычный подход.

                И совершенно естественно, что неэффективную систему поддерживать дорого. Но ведь можно сделать и эффективно.

                Плюс, они зачем-то имеют зоопарк шин (печальный опыт, не недостаток микросервисов).
                  0

                  А вторая половина проблем в цитате из спойлера — из-за отсутствия или недостатка тестов и документации.

                    +2
                    Тоже хотел сказать об этом, но тут есть доля правды. Юнит-тесты не спасают от ломания всего при смене логики какого-то сервиса. Так что тут либо соблюдать compatibility promise, либо страдать. Собственно, там описываются страдания от первого варианта.

                    Но вот лично у нас gprc, и, как мне кажется, изменения логики при сохранении совместимости — не такая уж и тяжелая задача. Возможно, только пока.
                      +1

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


                      1. Предполагаем, что виноват сервис "А"
                      2. Видим точку выхода в сервис "Б"
                      3. Читаем документацию сервиса "Б", убеждаемся, что ответ от "Б" не совпадает с документацией
                      4. Пишем тест на код, показывающий, что сервис "А" работает корректно
                      5. Закрываем папку с сервисом "А", открываем "Б"
                      6. Переходим к первому пункту

                      Такой подход решит все проблемы из спойлера-комента, которые не зависят от деплоя. А остальное решается правильным и автоматическим развертыванием.


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

                        0
                        собственно, эта ветка комментариев четко дает понять, что в программировании и, тем более, в проектировании архитектуры не существует «silver bullets» — любое решение должно быть аргументировано. И прямые руки и голова тоже нужны при любой архитектуре. Такие статьи и такие комментарии как раз и нужны для того, чтобы помочь не наступать на грабли, на которые другие уже наступили
                      0
                      Проблема в том, что система у нас очень большая, с кучей взаимосвязанных компомнентов, одним словом, сложная.
                      Микросервисная архитектура позволяет уменьшить сложность конкретных микро-приложений, но при этом возрастает сложность на уровне интерфрейсов между приложениями, так что общая сложность системы в целом не изменяется.
                      Этот перенос сложности с одного уровня на другой имеет как плюсы (упрощение конкретных приложений-микросервисов), так и минусы (из спойлера).
                      Поэтому для каждого конкретного компонента надо смотреть отдельно, как лучше его реализовать. А микросервис — это лишь один из инструментов, который может где-то в чем-то помочь.
                        +1
                        Извините, но вы сами не замечаете?
                        «с кучей взаимосвязанных компомнентов» как-то слабо кореллирует с «Микросервисная архитектура». По крайней мере в моём понимании — микросервисная архтиектура подразумевает независимость сервисов друг от друга на уровне логики. Возможно при разбиении сервисов были допущены серьёзные промахи «где резать»?

                        Ну и как тут рядом говорят — невозможность локального разворачивания сервисов == хреновая схема деплоя.
                          0
                          Вот, например, пользовательский интерфейс и хранилеще данных, они попадают под термин «взаимосвязные компоненты»? Уверен, что да. И, чаще всего они реализуются как раз в виде микросервисов, и начали так реализовываться задолго до того, как появилось ругательство «микросервисы».

                          Так вот, если у вас эти компоненты реализуются в виде динамических библиотек, как это делают в «монолите», многие ошибки можно отловить на этапе компиляции, те же несовпадения типов и числа параметров.
                          Если же у вас всё в виде микросервисов, то такие банальные ошибки отловить не получится, так как всё будет завёрнуто в json\html или во что вам заблагорассудится. При этом далеко не всегда предворительные проверки отловят ошибочное место. При этом далеко не всегда происходит одновременная коректная правка узких мест. При этом далеко не всегда удастся отловить подобные ошибки с помощью тестов, если, например, тесты не были вовремя обновлены или если передаваемые данные носят необязательный характер.

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

                          Конкретный пример? Приложение, рассчитывающее какой-нибудь сложный интеграл, основное приложение, задающее часть параметров по свойствам описываемого объекта, и система ГИС, отвечающая за требуемые координаты. Плюсы, шарпы и html с нодом.
                          Интегралы, с которыми сталкиваются, к примеру, экологи, могут иметь под 50 параметров, 49 из которых необязательны и 15-20 используются очень редко, а сама формула периодически обновляется, приобретая мелкие нюансы, дающие разброс до 10% и больше. Да даже просто переименовываются некоторые параметры, это нужно вносить в приложение, потому как искать через 10 лет историю переименования параметров формулы — проще всё написать с нуля. Вот и получается, что ни тестов железобетонных написать, ни заморозить API не получается, тестирование становится беззубым, а единственным надёжным помощником в отладке становится калькулятор. Ну ничего себе, технический прогресс!
                            0
                            > как появилось ругательство «микросервисы»

                            Простите, но это явное предвзятое мнение. Основанное на неудачном опыте… вследствии криворукости.

                            > Так вот, если у вас эти компоненты реализуются в виде динамических библиотек, как это делают в «монолите», многие ошибки можно отловить на этапе компиляции, те же несовпадения типов и числа параметров.
                            Если же у вас всё в виде микросервисов, то такие банальные ошибки отловить не получится, так как всё будет завёрнуто в json\html или во что вам заблагорассудится. При этом далеко не всегда предворительные проверки отловят ошибочное место.

                            Т.е. с валидацией данных/интерфейсов вы не знакомы? Json-schema для json и xsd для xhtml для вас ни о чём не говорят? Проблемные места должны быть покрыты валидацией. Интерфейсы должны иметь взоможность валидации на этапе разработки (как минимум).
                            Если ваше окружение уровня домашней поделки — там оно может и не надо, а вот коммерческая разработка таки требует таких вещей. Посмотрите на SOAP — там интерфейс описывается схемой и от этой схемы строится клиентское и серверное приложение! В REST (json) — это так же возможно.

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

                            Простите, но что значит «одновременная корректная правка узких мест»? Интерфейсы должны поддерживаться в единообразии ВСЕГДА. Внутренняя логика НЕ ИМЕЕТ права ломать контракт интерфейсов. Всё остальное решается достаточно легко.

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

                            Не поверите — делал ошибки в монолите. Опечатки! Обнураживалось не всегда сразу — несколько раз уходило до Q&A. После этого полной уверенности, что не ушли в прод — у меня нету. Для любых систем необходимы гарантии соблюдения контрактов интерфейсов. Будет это в монолите или в микросервисах — накосячить можно всегда. Валидировать тоже можно всегда.

                            Ваш конкретный пример показывает стандартную проблему «надо было сделать ещё вчера». Некоторые компании действительно работают по такой схеме. Я бы не хотел пользоваться их софтом вне зависимости от того, будет ли там монолит или будут микросервисы. Этого нужно избегать. Как? Например версионирование и валидация интерфейса — в пределах одной версии интерфейса мы гарантируем его состояние и проверяем корректность данных валидацией. Возможно конкретная ситуация требует ещё каких-то действий. Но проблемы тут явно не из микросервиски растут.
                              0
                              > Простите, но это явное предвзятое мнение.
                              Да это вообще не мнение, а фигура речи. У меня нет никаких претензий к инструментам, включая микросервисы, у меня они только к евангилистам, которые взялись инструмент узкого применения проповедовать, как очередную серебряную пулю.

                              > /* о валидации и ошибках */
                              Ошибки можно допускать всегда и везде, и очень часто они проживают достаточно долго. Другое дело, что добавляется ещё один уровень, на котором эти ошибки могут допускаться.
                              Валидация — это хорошо, это очень хорошо, почти так же хорошо, как и юнит-тестирование, но это тоже не серебряная пуля. И в файлах валидации тоже можно допустить досадные ошибки. Причём, такие, что, утрируя, из-за пропущенного слеша или запятой валидация будет успешной даже на левых данных. А ошибочные интерфейсы и запросы проверяют ох как редко.

                              > Ваш конкретный пример показывает
                              ничего. Есть области, вроде юриспруденции, в которой любое своевременное решение, каким бы плохим оно не было, гораздо лучше отсутствия этого решения. Я не буду из себя строить особого знатока энтерпрайза, однако, я практически уверен, что области, в которых можно спокойно натирать свой код пастой ГОИ встречаются на порядок реже тех, в которой использование бензопилы вместо скальпеля будет мерой, адекватной текущей ситуации.

                              В целом, я не склонен с вами спорить в том, что правильно спроектированные микросервисы могут быть очень удобны и полезны, ибо есть никсовый терминал и огромный список BSD утилит, делающих ровно по одной вещи, но хорошо. Я просто напомню вам, что, какими бы хорошими они не были, GNOME написан не на них. Как бы ни были хороши микросервисы, внедрять их имеет смысл только там, где накладные расходы от их внедрения и работы окажутся меньше текущих. А если дешевле по старинке подгружать библиотеки и шарить данные между потоками, не надо выискивать способ этого не делать.
                            0
                            У нас в компании есть таки пример идеального микросервиса — конвертор XML в PDF: никаких зависимостей или связей. Многие приложения его успешно используют чтобы отрендерить печатную версию произвольной страницы.
                            Все остальные более или менее связаны: сервис учета покупок не может без сервиса покупателей, склад не может без покупок и покупателей, учет гарантийного обслуживания не может без всего перечисленного, и т.п.
                            Ну хорошо, на компоненты побили, все в разных базах, масштабируемо и т.п. Красота. Потом бизнес начинает требовать отчеты, в которых эти все данные надо опять соединить. И тогда надо либо внедрять Data Warehouse, либо самим писать интерфейсы для передачи данных в общую базу, либо писать отчеты, которые смогут тянуть и Join-ить данные из разных БД без лишних тормозов. И то, и другое, и третье дорого, как в плане разработки, так и особенно поддержки.
                            Поэтому общая цена может оказаться сильно выше.
                              0
                              Конвертор XML в PDF — а это как? XML же не имеет визуального представления своих данных… или есть какой-то шаблон, а по нему строится pdf-документ? Или имелось в виду xhtml?
                      0
                      А разве Microsoft Transactions Server c DCOM объектами не этим же занимались?
                        +5
                        Все эти статьи про микросервисы не учитывают одну важную деталь — если у вас монолит из говна и палок, то распилив это поделие на микросервисы ничего хорошего не выйдет:

                          0

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

                            +2
                            Вот в этом я не могу согласиться. Если эти сервисы взаимодействуют между собой (не важно, обычные сокеты, или http поверх, или очередь какая-то вроде RabbitMQ) и нужно организовать их доступность, то всё очень усложняется. Тут помимо разработки ещё усложняется администрирование и т.п. Так что, не всё так однозначно.
                          0
                          Вообще тема это Self-Contained Systems — и модульное и не настролько раздробленное. Модульность продиктована бизнес функционалом, а не желанием каждую функцию в сервис обернуть.
                          В микросервисах главная проблема — гранулярность и повышенное желание поболтать друг с другом через сетевые интерфейсы. На коленке всю эту инфраструктуру поднять тяжеловато будет — приходится использовать всякие Azure Service Fabric(который весьма неплох). Это все в любом случае усложняет процесс разработки, отладки и администрирования. Для всего нужна золотая середина.
                            +1
                            А кто что использует в качестве шины, чтобы связать между собой весь ворох этих микросервисов и минимизировать latency? RPC какой-нибудь поверх rabbitmq или что-то ещё?
                              0
                              У нас лично сервис дискавери через consul.io
                              Сами сервисы обмениваются кто по обычным tcp сокетам, кто по http между собой. В некоторых местах использовали RabbitMQ, но решили что обычные http запросы с keep-alive соединением как-то попроще.
                                0
                                Для RPC есть gearman, хотя он выглядит заброшенным, я для одного проекта использовал этот велосипед (на GoLang), работает стабильно. В плане RPC он быстрее чем RabbitMQ в несколько раз.
                                  0
                                  zeromq
                                    0
                                    zeromq можно, но для RPC он не очень, вызовы блочатся на разрывах, в итоге приходится городить костыли вокруг.
                                  +1
                                  Вот почему когда я читаю такие статьи мне представляется икающий Роберт Мартин?

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

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

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

                                  «Отказоустойчивость», «распределенность», «масштабируемость»

                                  Это тоже просто фичи проекта, которые дешевле реализовывать поверх хорошего кода.
                                    0
                                    Выводы напомнили как Лесли Нильсен в роли президента США готовился произносить речь «Подъем экономики — хорошо, спад экономики — плохо...» :)

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

                                    Самое читаемое