Pull to refresh

Comments 30

Микросервисы никогда не были методом создать хорошую архитектуру. Они были (и являются) методом разделения компетенций. Люди с высокой компетенцией занимаются архитектурой и критическими сервисами, а второстепенные можно спихнуть на менее квалифицированных людей, обложив их фашистскими спецификациями и стандартами.

Главная разница между сервисной и микросервисной архитектурой в том, что микросервисное деление позволяет доводить этот процесс до «сервис на таску» или «сервис на человек».

Ценой же решения является вываливание ответственности (очень большой ответственности) за архитектуру на компетентных людей. Если кто-то не знает какая должна быть архитектура проекта, то сделав её микросервисной, человек получит куда более ужасную проблему, чем с кривой архитектурой в паре сервисов или монолитном приложении.

Перевод взаимодействя компонент с «внутри приложения» на сеть открывает новый класс ошибок — transient errors, вываливающиеся на operations. transient errors обладают таким свойством, что после того, как они случились, их нельзя отладить — они самопроходят. Просто что-то где-то флапает иногда. Достаточно, чтобы это раздражало и мешало, не достаточно, чтобы быть уверенным, что «оно не работает».

Хорошо сказано, но это не единственное основное свойство/задача микросервисов.


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

Вот тут вот вы делаете ошибку. Если у вас образовался «комок грязи», то он в любом случае будет мешать разработке (потому что программист не будет знать где что брать и откуда что ожидать). Но если в монолитной архитектуре это головная боль только программистов, то в микросервисной — ещё и operation'а, который вынужден выяснять кому куда там надо, а кому не надо, и в какой момент неожиданно кто-то получает отлуп от контрека, исчерпание портов на исходящих соединениях или просто флапающий DNS.

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

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

А почему (при прочих равных) вынос комка грязи на уровень сети ускорит разработку?

Допустим, у меня компонента А хочет итерировать по потрошкам объекта из компоненты Б, и мы плохо понимаем все случаи, когда она это делает и что именно она там ищет.

Раньше у нас A шарился в коде в Б, теперь, когда у нас тренд на микросервисную архитектуру, мы пишем RPC в Б, которое позволяет сделать get_all и сериализует свои потрошки. A периодически делает get_all, и что оно с ним делает — не понятно. А ещё A иногда меняет Б, так что Б реализует запрос update, имеющий форму eval() (ибо никто не понимал что делать и сделали как смогли).

Дано: микросервисная архитектура с хорошо описанным интерфейсом: get_all и update.

Кому от этого легче стало?

Это происходит потому, что этот RPC с большей вероятностью не добавят, нежели добавят. А в монолите это сделают наверняка. Происходит так потому, что добавить новый вызов RPC — намного дороже. Требуется координация между командами разрабатывающими разные микросервисы, требуется доказать команде отвечающей за Б что новый RPC реально необходим, требуется подождать пока эта таска будет реализована командой Б (а у них свои таски и приоритеты), требуется обновить документацию на API, в процессе есть высокий шанс огрести от архитектора который заметит эту активность, требуется добавить на дашборды мониторинг и алерты для ошибок нового RPC, etc. Дорого. Очень. (Причём это мы говорим про добавление RPC, а нередко хочется изменить существующий, что ещё дороже если изменения не совместимы.) А в монолите это сделать, наоборот, крайне дешево.


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

координация между командами разрабатывающими разные микросервисы

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

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

Получается как в анекдоте «сказал „отставить разврат“ и откопал стюардессу».

Нет, не переходим. Координация между командами — это не ключевой элемент, это просто одна из составляющих высокой цены (которая и является ключевым элементом) изменений API.


Если проект пилят 5 человек, то цена координации сильно уменьшается, но остаются все остальные составляющие цены. Если, к тому же, у проекта нет нормального архитектора, нет мониторинга, нет ревью, и практически отсутствует документация на API — вот тогда мы получим то, что Вы описываете: кошмар намного хуже монолита, причём если монолит был кошмаром только для разработчиков, то в случае микросервисов кошмар ощущают ещё и operation.

Вот, таким образом, без описываемых кусков, мы получаем кошмар, причём не в одном месте, а всюду.

А теперь последствия: в условиях, когда команда не справляется с архитектурой, притаскивание микросервисов делает хуже.

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


А в последнее время вообще стараюсь использовать "встроенные микросервисы", когда микросервисы проектируются так же тщательно, но при этом они остаются внутри монолита. Их реализация полноценно изолируется, другим частям монолита недоступно ничего помимо API (обычные вызовы функций, без сетевого I/O) этих встроенных микросервисов (в частности, у каждого из них своя БД), но они не выносятся в отдельные сетевые сервисы пока в этом не появляется реальная необходимость. Это значительно снижает стоимость их разработки на начальном этапе, упрощает развёртывание, сильно упрощает изменение их API при необходимости (но это не только плюс, но и минус, как я упоминал выше, так что — только под жёстким контролем архитектора). Получается монолит, который всё ещё требует такой же высокой компетенции архитектора как и микросервисы, проектирование которого требует столько же времени как и проектирование микросервисов (потому что это оно и есть), развёртывание которого немного сложнее обычного т.к. ему нужно передать много отдельных параметров для всех встроенных микросервисов (включая подключения к разным БД), но который, тем не менее, сохраняет многие плюсы микросервисов и обходится значительно дешевле полноценных микросервисов, несмотря на возможность довольно быстро на них развалиться.

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

Когда неуёмный креатив команды загоняют в загончик — не важно, в виде сервиса, слоя, интерфейса, компонента или класса — это называется архитектура. И вот когда её нет — тогда команду точно ничего не спасёт.


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


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

Когда неуёмный креатив команды загоняют в загончик — не важно, в виде сервиса, слоя, интерфейса, компонента или класса — это называется архитектура. И вот когда её нет — тогда команду точно ничего не спасёт.


Верно! А вот все остальное совершенно не верно. Навскидку
— Непонятно как замена MyModule.MethodA на HttpClient.Get(«mymodule/methoda») внезапно влияет на архитектуру?
— Непонятно, что мешает архитектору спроектировать интерфейсы между модулями также, как если бы он делал микросервисы и получить все преимущества микросервисной архитектуры без ее недостатков?
— Средний проект на 1 000 000 строк, микросервисов по 500 строк будет 2000. Почему бардак из 2000 модулей будет, а бардака из 2000 микросервисов не будет?
— Почему тестировать модуль «сложно», а тестировать микросервис просто?
— Почему микросервисы параллельно разрабатывать можно, а модули нельзя?

В теории Вы правы, внутри монолита можно организовать ровно то же самое, что и в микросервисной архитектуре. И такие попытки регулярно случаются. Я и сам сейчас пытаюсь это делать. Но — это в теории. На практике возникают всякие факторы, которые всё портят.


Основной фактор в том, что в случае микросервисов разработчикам очень сложно "срезать углы" и нарушать архитектуру, а в случае монолита это намного проще… и дальше в дело вступает фактор человеческой природы (лень и нежелание вникать в абстрактно-архитектурные концепции если виден способ решить проблему прямолинейно) и фактор бизнеса (желание получить результат быстрее и дешевле). Поэтому, по крайней мере из моего личного опыта на данный момент, на практике подход "встроенные в монолит микросервисы" работает только при условии, что ЯП и/или используемые инструменты активно поддерживают этот стиль — т.е. делают "срезание углов" более дорогой/сложной/заметной операцией, чем решение задачи без нарушения архитектуры. Многие ЯП не поддерживают необходимую для этого степень изоляции разных частей кода внутри монолита. Даже в новых языках вроде Go поддержка необходимых для этого internal пакетов появилась не сразу, а только в версии 1.4 (4 года назад).


Почему бардак из 2000 модулей будет, а бардака из 2000 микросервисов не будет?

Ну, микросервисов будет заметно меньше 2000 (потому что в большинстве микросервисов кода будет заметно больше 500 строк), но пара сотен — запросто. И бардак там будет обязательно. Разница в том, что бардак будет на уровне связей между микросервисами, и этот бардак будет головной болью архитектора в намного большей степени, чем головной болью разработчиков. А вот бардака в коде отдельных микросервисов будет намного меньше. Иными словами можно будет намного дольше сохранять высокую скорость добавления в проект новых фич и исправления багов. (И если говорить не про традиционный монолит, а про монолит состоящих из тех же 200 "встроенных микросервисов", то ситуация не изменится — основной бардак по-прежнему будет на уровне связей между ними.)

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

Я микросервисами (без разницы, встроенными в монолит или внешними сетевыми сервисами) пытаюсь решить проблему экспоненциального замедления скорости развития проекта — за счёт усиления изоляции между компонентами-микросервисами код каждого из них получается намного проще и при этом слабо связан с кодом других, что и решает упомянутую проблему. Так же разработка микросервисов (опять же, без разницы встроенных или нет) позволяет сильно распараллелить разработку, т.е. сократить time-to-market без необходимости писать быстро/грязно. Я хочу писать качественный код, в идеале, заметно быстрее, чем пишется традиционный быстро/грязный код. (Да, я романтик, но я считаю, что это возможно.)


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

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

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

Как только происходит попытка поставить вопрос об архитектуре формально, математически строго, все споры заканчиваются. Становится ясно, что мы ничего об этом не знаем. Мы не знаем как описывать архитектуру, как сравнивать разные варианты, как гладко/непрерывно переходить от одного к другому и т.д. Где-то в ответах на эти вопросы зарыта «серебряная пуля» или строгое доказательство ее отсутствия.

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

Кто сказал, что сложность растет экспоненциально? Если так, то что конкретно движет ростом сложности? Если понять закон роста сложности, то скажет ли это нам что-то об объекте, сложность которого растет? Возможно ли, что мы сможем это знание применить, чтобы рост сложности замедлить? Что такое сложность, может быть это вещественное число, вектор, оператор или может быть комплексное число или кватернион?

за счёт усиления изоляции между компонентами-микросервисами

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

Не уверен, что понял Вашу мысль. Или это фраза из тех, где часть перед "но" не имеет значения? :)


Давайте я попробую прояснить, какое именно направление я взял. Я не считаю микросервисы "серебрянной пулей". Я не считаю, что все проекты надо делать на микросервисах, равно как не считаю, что нет проектов которые надо делать на монолитах. У любого подхода есть свой баланс плюсов и минусов, разным проектам лучше подойдут разные подходы. Что я пытаюсь сделать, так это подобрать нужный мне баланс плюсов и минусов для третьего подхода "встроенные микросервисы", потому что мне кажется, что он неплохо подойдёт для многих проектов, которые сейчас страдают либо от сложности (деплоя, сетевого I/O, …) добавленной микросервисами, либо от неприемлемого (экспоненциального) замедления скорости разработки монолита.


Нет такой архитектуры, про которую было бы известно, почему она работает и точные границы ее применимости.

На самом деле такая информация уже есть — она накапливается с годами опыта:


  • Для низкого уровня "архитектуры" такая информация уже давно всем известна: как писать небольшие модули, классы и функции мы знаем очень хорошо — есть "не используйте goto" и структурное программирование, есть ООП но "по возможности избегайте наследования не абстрактных классов/интерфейсов", есть SOLID, паттерны, есть функциональное программирование… плюсы/минусы и границы применимости всего этого известны уже достаточно хорошо.
  • Для среднего уровня есть MVC, MVVM и кучка их вариаций, есть Clean Architecture — и тоже вполне понятны границы применимости, что они дают, слабые места, и для каких задач они подходят, а для каких не очень. Ещё лет 10-15 назад такой ясности с ними не было, по нижнему уровню большинству было уже понятно почти всё (кроме наследования), а по среднему ясности почти не было.
  • Сейчас ясности почти нет только по высокому уровню архитектуры, когда у нас либо действительно большой монолит, либо проект под несколько десятков микросервисов.

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

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


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


Кто сказал, что сложность растет экспоненциально? Если так, то что конкретно движет ростом сложности?

Это видят все, потому что это происходит практически на каждом нетривиальном проекте, который продолжает развиваться. Почитайте умные книжки, там детально расписаны ответы на эти вопросы (напр. "Совершенный код" Макконнелла, что-нибудь дяди Боба, etc.).


Если понять закон роста сложности, то скажет ли это нам что-то об объекте, сложность которого растет?

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


Возможно ли, что мы сможем это знание применить, чтобы рост сложности замедлить?

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


Откуда известно, что усиление изоляции всегда ведет не к росту, а к уменьшению сложности?

Оттуда, что изоляция избавляет от необходимости держать в голове внутренние подробности реализации изолированных сущностей: когда мне нужно использовать сторонний микросервис или библиотеку мне достаточно держать в голове их API, а подробности их реализации мне не нужны (кроме ситуаций, когда плохая изоляция приводит к "протечке абстракций", и подробности их реализации внезапно становятся важны).


...

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

Это происходит потому, что этот RPC с большей вероятностью не добавят, нежели добавят.

В результате в микросервисной архитектуре разработчики более склонны искать способы решить проблему используя уже существующее API

Вы всё верно говорите. Но стоит учесть человеческий фактор — лень и стремление делать меньше. И поэтому частенько вижу и отклоняю желания разработчиков сделать generic API вида get_all / update (eval) как подсказал amarao.
И такое происходит сплошь и рядом. И если за этим не следить (привет микросервисные Архитекторы), то «комок грязи» быстро дает о себе знать.
--и кратно ухудшает жизнь отдела эксплуатации.

Судя по моему опыту как раз становиться легче. Изрядная часть проблем вида «в большой системе что то сдохло — пускай программисты читают логи» превратились в «Сервис Б перестал обрабатывать задачи от А, пускай программисты читают логи сервиса Б». При этом задачи ждут в кафке и как починят — будут обработаны.
а потом окажется что виноват сервис В из-за того что упал сервис Г от ошибки в сервисе Д… но это уже выяснят программисты… ну тоесть как обычно.
В конце концов всё всегда как обычно, слава Тьюрингу. Однако до наступления этого счастливого конца есть ещё ряд этапов, некоторые из которых стали заметно проще.
Сервис Б молча иногда жрёт задачи от А, А иногда молча не присылает задачи в… а куда он их слать должен? Документация? Ты сделал мой день, такой смешной шутки я давно не слышал.

… Нет, подожди, там же есть старая версия Б, которая вообще сама делала всё, а не требовала чего-то от A, так что мы сделали так, чтобы когда A выкатилось, мы просто чистили очередь задач от него, а новая должна начать их использовать.

(вы неявно предполагаете, что operation хорошо понимает архитектуру получившегося комка грязки. Он потому и комок, что его никто не понимает).
Сервис Б молча иногда жрёт задачи от А

Жрёт в смысле принял, потерял, а сказал что обработал?

А иногда молча не присылает задачи в… а куда он их слать должен?

В топик кафки, за состоянием (скорость наполнения и статус обработки) мы получаем бесплатный мониторинг.

Ты сделал мой день, такой смешной шутки я давно не слышал.

Интернета не было или просо отпуск?

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

Сам так не делаю и другим не советую.

вы неявно предполагаете, что operation хорошо понимает архитектуру получившегося комка

Я явно знаю что понимание улучшилось. Естественно не по тому что какая то особая «микросервисная магия», а потому что одновременно с распилом монолита происходит рефакторниг.

Ох… А вы точно "банк"? Где же все эти традиционные soa и esb? Выглядит так что вы переизбрали их, только менее надёжные (очередь вместо шины и нет распределенных транзакций)


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


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

Последний вопрос называется «невозможность выполнить что-либо ровно 1 раз в распределённой системе». Либо at least once, либо at most once.

Я в курсе)
Решил, что с конкретным примером будет понятнее

Ох… А вы точно «банк»?
Где же все эти традиционные soa и esb? Выглядит так что вы переизбрали их, только менее надёжные

Охи трудно комментировать.
A ESB нет из разных вариантов решения проблем выбрали тот, который описан.

(очередь вместо шины и нет распределенных транзакций)

Есть распределенные транзакции но не между сервисами, а внутри. А нет их между сервиссами т.к. сделать полноценные распределенные транзакции при такой архитектуре трудно.

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

1) Каждое отправляемое сообщение снабжается UUID. При получении сообщения его UUID сравнивается с уже полученными и включается логика сервиса. Либо сообщение отбрасывается либо обновляется состояние в БД.
2) По возможности транзакция не закрывается, пока не получен ответ от кафки.
Отвечу уклончиво — по питону в анамнезе и необходимости делать свою квази-ESB должно быть ясно что из первых с конца всевозможных рейтингов :)
Sign up to leave a comment.

Articles