Re[14]: Помогите правильно спроектировать микросервисное приложение
От: Sinclair Россия https://github.com/evilguest/
Дата: 01.02.26 13:16
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Да, я об этом и писал. Если все микро-сервисы могут работать независимо друг от друга, то такая архитектура жизнеспособда.

G>Но тогда важное условие чтобы транзакционный бизнес-процесс не пересекал границы микросервисов. Как только начинает пересекать — появляются зависимости и одно без другого уже не работает.
С транзакциями да, нормального решения для распределённых транзакций не существует. Зато есть идемпотентность.
G>Например для ecommerce деление на "заказ" и "склад" в таком случае не актуально, так как при создании заказа надо на складе все забронировать.
Либо у нас появляются разные заказы: один на стороне "корзинки", и другой на стороне склада — при его создании выполняется автоматическое резервирование.
А сервис корзинки (или там оркестрирования) как раз и занимается тем, что идемпотентно долбит "склад" запросами на создание "резерва" до тех пор, пока не получит внятный ответ.
G>А вот "доставку" от "магазина-склада" вполне легко отделить.
Ну, тут опять — ведь у нас транзакция должна и товар из "резерва" в "отгружено" перевести, и в доставке "транспортную накладную" породить.


G>Это ровно до тех пор пока БЛ не начинает опираться на роли пользователей.

G>Например: сотрудник магазина получает скидку 10% на все покупки в онлайн магазине. Чтобы решить эту задачу сервис "заказа" должен пойти в "auth", чтобы получить роль.
Либо просто посмотреть набор ролей в JWT, который был порождён auth при логине.

S>>КМК, это не какие-то "новые" проблемы из-за МСА, а все те же проблемы, которые прекрасно работали и в монолите. Висение в ожидании возможно примерно везде, где есть await (то есть везде). Получение некорректных данных — да запросто, если разработчик воткнул какие-нибудь try {return calculatedTax()} catch { return defaultTax }.

G>Вот именно. Чтобы в монолите получить некорректные данные надо что-то специально написать. В МСА надо специально писать чтобы данные были согласованы.
Не очень понятно, что имеется в виду. Если у нас в микросервисе нет аналогичного кода с try / catch, то обращение к упавшему сервису просто каскадно отдаст 500 internal error, а не неверные данные.

G>За счет чего? Если мы делаем синхронный вызов, то у нас надежность сервиса, зависит от надежности другого и в сумме не превышает надежность монолита.

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

G>Если мы делаем асинхронную репликацию изменений, то отдаем неверные данные.

Непонятно, что вы называете "асинхронной репликацией изменений". Вы имеете в виду, что я могу видеть некорректные остатки на складе из-за того, что у меня в "корзинке" есть заказ в статусе "резервируется"?
Ну так это вопрос к тому, что мы вообще называем верными данными. Потому что ответ на вопрос "а есть ли у нас сейчас в наличии на складе PlayStation 5 pro" в распределённой среде очень сильно зависит от трактовки термина "сейчас".

G>Основной агрумент против, это то, что МСА сама по себе ничего не дает и очень многое отнимает.

G>Все проблемы, которые призвана решать МСА, прекрасно решаются и без МСА. Кроме, наверное, проблемы разработки на нескольких языках программирования.
Ну, "прекрасно" — вопрос спорный. Масштабируемость, к примеру, штука такая, которую трудно решить монолитом. Контраргумент к этому тоже известен: в современную коробку влезает настолько много дури, что хорошо написанный монолит справляется в ней с нагрузкой, которую "красивая МСА" тащит тремястами машинками в облаке.
Но я видел и ребят, у которых такие масштабы нагрузки и объёмы данных, что даже самая передовая современная коробка пробуксовывает. И решают они это именно тем, что пилят систему на мелкие неэффективные запчасти.
То есть делаем в десять раз более прожорливую по ресурсам реализацию, и отдаём ей в сто раз больше ресурсов — получаем таки десятикратный рост пропускной способности по сравнению с топовым сервером.

G>И то, например, сейчас можно в дотнет процессе запускать Python код. Это не IronPython, который в IL компилирует, а это прямая загрузка питонячего рантайма в дотнет, генераторы, которые делают C# АПИ для питонячего кода и прокидывание ссылок на объекты в из дотнета в пайтон.

Да зачастую проблема языков не в языках, а в том, что одна команда уже переехала на Java 22, а другая всё ещё пердолится с Java 17. И как запустить их код в одном JVM — вопрос интересный.

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

G>>>Теперь нам надо сделать рассылку, подобрав подходящие маршруты для клиентов.
G>>>В монолите — один джоин. В MSA — пожалуйста напишите тонну кода.
G>>>В реальности ситуация была еще хуже. Данные о бронях групп и совершенных путешествиях лежали в третьем сервисе. И конечно же надо было из предложений исключить тех кто уже ездил.
S>>Тут дело, КМК, не в тонне кода — что там такого-то, цикл по пользователям. Скорее, тут время работы — то, что делалось 1 запросом за 15 минут, теперь — миллион обращений к двум сервисам, каждый из которых тратит по две секунды.
G>И как это решить? Там отдельный сервис identity, который занимается аутентификацией и хранит профили (раньше кста отдельный сервис user был, но слили), и отдельный сервис БЛ.
В смысле "как"? Получаем список профилей, и идём по нему в цикле. Можно порезать его на 20 батчей и идти по ним параллельно на 20 машинах.
В общем-то, join делает то же самое — K операций стоимостью O(logM).

G>Ну камон, фичафлаги же есть.

G>Даже в микросервисах часто приходится релизить с фичафлагами по тем же причинам.
А причём тут фичафлаги? Они ортогональны обсуждаемой проблеме. Фичафлаг не автоматизирует ребейз моего "баннера" к "предыдущему релизу".

S>>Нет такой идеи, поэтому и противоречить нечему.

G>Я выше описал почему в МСА микросервис должен быть самодостаточным, иначе проблем от МСА больше, чем преимуществ.
Никакой сервис самодостаточным быть не может — ни микро, ни макро. Потому, что иначе это не "сервис", а целое отдельное приложение. Пользовательский экспириенс так или иначе пересекает границы таких "сервисов".
Даже если мы возьмём какой-нибудь, прости господи, Яндекс, у которого в одном приложении и такси, и лавка, и кино — все они зависят от ID-сервиса, т.к. без логина я ни в один из них не попаду.

G>Звучит так, что недостаток ревью и тестирования пытаются решить не на том уровне.

Усиление ревью и тестирования ещё сильнее удлиняет цикл релиза.
Помнится, была весёлая история про то, как пилили фичу про шатдаун в винде 10. Там от коммита в kernel до возможности протестировать кнопку в explorer.exe проходило три месяца — несмотря на отсутствие микросервисов и монолитность.

S>>Во-первых, может и балуемся, во-вторых, вы же не ведёте разработку в main. Вот ребята в В залили в монорепу изменение, которое сломало сценарий 2. "У нас юнит-тесты зелёные, а если у кого-то сломался сценарий — значит, они нас неправильно используют". Им дали по пальцам, откатили PR. Ребята из Б залили свой PR — сломался сценарий 1. И ребята из А заливают свой PR с тем же результатом.

G>У нас все просто — кто делает ПР тот и чинит. Тупо защита на ветке — не сольешь пока есть ошибки билда (там еще и тесты запускаются, но у нас их мало).

S>>Предотвращение мерджа ломающих изменений — это чисто организационная работа. То есть если у нас есть интеграционные тесты со всеми сценариями И запрет мёрджа PR, при котором ломаются такие тесты — то всё будет работать и в монорепо, и в раздельных репозиториях. А если набора интеграционных тестов нет — то и в монорепо у вас окажется ситуация, когда все юнит-тесты зелёные, покрытие кода 99%, а ни один пример из папочки examples запустить не получается.

G>Я бы сказал что это чисто настройка ci\cd системы, конечно пока все в одной репе происходит.
Я и говорю — всё это сводится к настройке ci/cd. Если в ней есть интеграционные тесты, то неважно, монорепо там, или микросервисы с отдельными репозиториями, или всё одно приложение в ./src.
А если нету — то никакой монорепо вас не спасёт.

G>Как только у вас требуется коммит в несколько реп для реализации функционала — начинаются танцы с бубнами (реальное описание "организационной работы")

Ну, может быть.

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

G>Тогда нужно отказываться от МСА. Потому что МСА добавляет огромное пространство для неочевидно неправильных решений. В рамках монолита аналогичные решения были бы очевидно неправильными.
G>Выдача неправильных данных при ошибке одного из сервисов как раз из этой области.
Я всё ещё не понимаю сценарий выдачи неправильных данных.

G>ИИ? Линтеры? Показательно бить палками тех кто так пишет?

G>Зачем для решения плохого кода на TS делать МСА?
Потому что радикальные решения иногда эффективнее теоретически правильных, но необязательных.
Вроде всем разработчикам уже тридцать лет долбят "API first", но пока фронтенд живёт "рядом" с API в том же ASP.NET приложении, почему-то всегда появляются волшебные скрины, которые дают сделать невозможные через API вещи.
И только хардкорная смена архитектуры на "фронт ходит к нам в бэк только через тот же API, который мы отдаём наружу", заставляет волосы стать мягкими и шелковистыми.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.