Re[9]: Помогите правильно спроектировать микросервисное приложение
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 02.04.25 07:45
Оценка: 80 (1)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, gandjustas, Вы писали:


G>>В managed среда это можно и без микросервисов сделать.

S>Не очень понятно, что вы имеете в виду. Поддержку AppDomain выпилили (за ненадобностью) из нового дотнета, а в жаве её никогда и не было.
S>Управляемая среда всего лишь гарантирует отсутствие ошибок типизации и "повисших" ссылок. Всё. Никакой изоляции она из коробки не предоставляет.
S>Понятно, что она лучше любого анменеджеда хотя бы тем, что в ней нет undefined behavior (если, опять же, придерживаться гигиены и не пользоваться всякими ансейф и около-ансейф вещами).
Я говорю о том, что вручную выстроенная модульность внутри приложения, на уровне отсутствия ссылок из группы объектов, поддерживается самой managed средой. Без использования ансейфа и разделяемых данных без должной осмотрительности крайне сложно в managed среде во время обработки одного запроса нарушить работу кода, обрабатывающего другие запросы.


S>Но defined behavior недостаточно для ограничения failure domain. Потому что если есть разделяемое состояние, то невозможно гарантировать его целостность при наступлении неожиданных результатов.

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

Очень хороший кейс.

А давай прикинем как это будет в MSA. там будет точно такая же коллекция в одном из сервисов, и менять её будет не поток, порожденный программистом, а поток порожденный хостом для обработки запроса. А все остальное будет то же самое. И, как в случае монолитного приложения, так и в случае MSA падения как такового не будет. Восстановление после такого falure возможно только сбросом состояния путем насильного перезапуска, например по хелсчеку. Разница в случае MSA будет только в том, что будет перезапущен один маленький сервис, а не толстое приложение. Но при этом весь сценарий работать не будет, пока микросервис с разделяемой коллекцией в памяти не оживет.

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




G>>А даже в случае unmanaged можно разделить по процессам и использовать unix-sockets\named-pipes для общения между ними в рамках одного сервера, обеспечивая все взаимодействие и синхронизацию разных инстансов (серверов\контейнеров) через базу.

S>Всё верно. Если у нас shared state вынесен в "базу", которая предоставляет ACID-гарантии, то у нас масштабы проблемы ограничены.
При горизонтальном масштабировании к этому все и придут обязательно. В принципе грамотный архитектор должен сразу на такое натолкнуть.

S>В предположении, что все инварианты инкапсулированы в этой базе. На практике, зачастую собственно перемещение логики из базы в "сервисы" и делается ради того, чтобы обеспечить те инварианты, которые в базе поддерживать оказывается тяжело. То есть у нас опять корректность разделяемого состояния определяется тем, насколько корректно себя ведёт каждый сервис. И наличие бесконтрольного доступа в базу приводит к тому, что ошибка в сервисе А приводит к записи в базу мусора, который нарушает предположения, на основе которых реализованы другие сервисы.

Опять-таки MSA ничем не помогает в этом случае. Без разницы будет мусор писать в базу микросервис или монолит.

G>>Если говорить failure domain с точки зрения логики приложения, то разделение на микросервисы делает ситуацию только хуже. Так как разработчики одного микросервиса могут внести изменения, ломающие работу других и даже не узнают об этом. Поэтому с точки зрения бизнес-логики идеально чтобы бизнес-процесс начинался и заканчивался в одном микросервисе.

S>Это уже административные вопросы. Вы неявно предполагаете, что единицей тестирования является отдельный микросервис, из-за чего бизнес-процесс, пересекающий границы микросервисов, останется непротестированным.
Так это самые важные вопрос. Я еще раз напомню тезис, который в этой теме и другие коллеги подтверждают: MSA это больше про оргструктуру, а не про архитектуру. В интернетах предлагают "правильную" MSA с разделением микросервисов по разным репозитариям исходного кода. Как в этом случае обеспечить синхронность изменений разных микросервисах, если изменяемый бизнес-процесс затрагивает несколько таких микросервисов?

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

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

S>А если у нас процесс тестирования построен (хотя бы частично) вокруг бизнес-сценариев, то нам всё равно, сколько там компонентов, сервисов, или микросервисов в нём участвует.

В msa весьма вероятна ситуация, что в принципе невозможно собрать работающее сочетание микросервисов. Я такое на практике видел. Было три микросервиса (А,Б,В) и два процесса(1,2), затрагивающие три сервиса. В один момент было так, что: А/HEAD, Б/HEAD, В/HEAD~1 работал сценарий 1, но не работал 2. А/HEAD, Б/HEAD~1, В/HEAD работал 2, но не работал 1 и при этом же А/HEAD~1, Б/HEAD, В/HEAD также работал 2, но не работал 1.

И каждый разработчик миросервиса доказывал что "work on my microservice" (современный аналог work on my machine)


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

Скажем так: в микросервисах сложнее внести зависимость. Иногда это выгоднее, но иногда нет, когда зависимость нужна.
Дисциплина, современные среды, языки и библиотеки (я не про Go) позволяют создавать достаточную изоляцию и внутри монолитного приложения, не получая огромных затрат на внесение зависимостей когда они нужны.

S>То есть пока у нас всё лежит в монолите, сотрудник команды А имеет возможность подсмотреть в код команды Б, и напрямую завязаться на то, что интерфейс IFoo реализован некоторым классом FooImpl. И вместо того, чтобы обратиться по стандартным каналам к архитекторам команды Б "добавьте мне в IFoo метод по расчёту предварительной цены без заключения сделки", делает даункаст и вызывает потрошки напрямую.

Доункаст к чему, если у него нет паблик класса, к которому даункастить?

S>Когда вы распиливаете монолит на процессы и пайпы, такой возможности уже нет — у Foo своё адресное пространство, и мимо протокола к нему обратиться не получится.

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

S>Зато можно полезть напрямую в базу "я же знаю, в каких таблицах у них что лежит". В итоге команда Б, полагая себя единоличными владельцами схемы, что-то перекраивает в очередной версии своего сервиса, и код команды А начинает падать, хотя такой же код неделю назад прекрасно работал.

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

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


S>Не, это вы как раз рассказываете про SOA, где сервисы всё ещё представляют собой очень крупные блоки. А MSA — это как раз экстремальное развитие SOA, когда мы дробим задачу на ещё более мелкие единицы, которые не реализуют никакой бизнес-процесс, а, скорее, отвечают за некоторые кирпичики этого процесса. Например, у вас есть "ядро" — микросервис, который отвечает за финансовые транзации. Только переводы между счетами, и всё. У него очень жёсткие инварианты, и всё покрыто требованиями регулятора. Поверх этого вы реализуете функциональность типа "Выдача кредитов". Наличие отдельного ядра гарантирует вам, что криворукий джун, разрабатывая код бизнес-процессов "выдача кредита" и "внесение очередного платежа" не напорет с транзакциями и не сломает базовые гарантии типа сохранения суммы всех балансов.

Я именно об этом и говорю. Знаю один банк где над таким ядром-"микросервисом" трудится команда из 40+ только программистов. То есть ни разу он не "микро", о чем я и говорю. А выдача кредитов это вообще с десяток связанных процессов, на каждом из которых команда в 10+ человек. То есть их тоже "микро" назвать сложно.

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

S>Дополнительными бонусами являтся возможность один писать на питоне, другой — на тайпскрипте.

Это какбы единственное неоспоримое преимущество MSA, с которым никто никогда и не спорил.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.