Как стать автором
Обновить

Комментарии 65

НЛО прилетело и опубликовало эту надпись здесь
Миграции должны иметь последовательность. Будет сложно предсказать результат если невыполненные миграции будут накатываться в произвольном порядке ибо одна миграция обычно зависит от другой. У нас бывает конечно такая проблема, что два и более разработчика работают в одном сервисе и пишут миграции. Но это случается очень редко и в таком случае у нас действует правило: «Кто первым встал — того и тапки». Касательно инструментов, некоторые команды в Lamoda еще используют goose, основное отличие только в том что миграции «вверх» и «вниз» описываются в одном файле.
НЛО прилетело и опубликовало эту надпись здесь
Как пример — django.

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

А ничего, что накат некоторых миграций не в том порядке может давать разные результаты?

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

Конечно, могу. Равно как и Вы можете эти примеры опровергнуть, сказав что они высосаны из пальца или что так делать не надо было и что есть другие варианты решения проблемы… но ведь на практике разработчики далеко не всегда делают всё правильно, и рассказывать постфактум о том, что существовало лучшее решение которое бы не привело к проблемам — бессмысленно.


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

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

Нетушки. На то есть ревью. Плюс вменяемый разработчик посмотрит сначала на 100_update.sql, и подумает, как эти изменения должны сказаться на его миграции 101.


Но самое главное вовсе не это. Баг создать можно, протупить с миграцией и ревью тоже можно — это всё нормально. Главное, что в результате везде (локально у всех разработчиков, на стейдже, на проде, etc.) будет этот баг с некорректной записью в БД. БД будет везде одинаковая, не будет проблем с откатом последнего PR или можно надёжно (на всех площадках) пофиксить этот баг в 102_update.sql.

НЛО прилетело и опубликовало эту надпись здесь
откатить миграции

откатить, лол, попробуйте откатить DELETE/UPDATE/ALTER на столбец. Скажем так — существуют миграции, которые возможно откатить, есть правила хорошего тона (писать чтобы все было откатываемым), но это совершенно не означает, что в реальном кейсе не случится какой-то факап.

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

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

НЛО прилетело и опубликовало эту надпись здесь

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


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

Пользователь 1 создает миграцию 100_insert.sql
пользователь 2 создает миграцию 100_update.sql

как минимум это так не работает. Вы те же миграции Алхимии под пайтон видели?

Ну у нас были проблемы, когда миграции затрагивали одни и те же таблицы.
Если два программиста одновременно работают над пересекающимися задачами — это уже плохая идея. Но даже в таком случае — если им нужно, например, добавить в таблицу новое поле, и они нормально именуют поля, вряд ли они назовут поле одинаково. А следовательно, нет никакой разницы, в каком порядке применять эти два альтера. Так же хорошая практика на дэйли рассказывать, кто над чем работает — это минимизирует вероятность, что два программиста независимо друг от друга создадут в одной таблице поле с одинаковым именем, заложив в него разную логику.
Если вы работает в разных ветках с одной базой и в обоих есть миграции, то ничто не спасет от наслоений и пред мержем всегда есть вероятность, что придется переименовывать миграции, если порядок вмерживания не совпадает нумерацией миграций.
goose, основное отличие только в том что миграции «вверх» и «вниз» описываются в одном файле.

На самом деле основное отличие в том, что goose поддерживает миграции в виде функций на Go, что важно, потому что далеко не всегда возможно описать требуемую миграцию на чистом SQL. И в каком проекте и на каком этапе это понадобится — заранее предсказать невозможно, поэтому лучше иметь такую возможность изначально, и пусть лучше не пригодится, чем наоборот.

Еще полезно, когда приходится менять очень много данных и чтобы не блокировать таблицу одной мегатранзакцией, из гоу менять порциями.
github.com/rubenv/sql-migrate
Вот рабочий вариант миграций с атомарностью и раздельным хранением.
Не так популярно, как go-migrate, но мне тож так больше нравится. (не только верхняя версия, а весь список примененных миграций к конкретной БД и когда они были выполнены.)
Из плюшек — миграции вшиваются в бинарник, и деплой базы упирается в указание, какая из нод должна накатить изменения. Требования обратной совместимости и не мигрировать всеми разворачиваемыми нодами одновременно естественно на релиз инженере.
github.com/lancer-kit/service-scaffold/tree/master/dbschema — пример интеграции.
(надеюсь, что не обидел автора топика разместив ссылки в комментариях)
Насколько я знаю migrate тоже поддерживает «вшивание» миграций в бинарник, через go-bindata. http://https://github.com/golang-migrate/migrate#migration-sources Мы у себя правда используем вариант с docker образом migrate и монтируем в него папку с миграциями (примерно также как в docker-compose.yml в статье). Все это осуществляется в рамках отдельной deploy'ной job'ы, вроде хватает. Но ваш вариант обязательно изучу.
НЛО прилетело и опубликовало эту надпись здесь
А потому что миграции с меньшей версией чем в базе не должны мигрироваться, потому что уже могут не соответствовать текущей структуре базы.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Изменения в ветке вы же тоже комитите или сташите, логично, что изменения в базе тоже нужно откатить. По этому у вас и есть down миграции.
Иначе как понять состояние базы, если база считает, что была миграция, а кодовая база не знает что в ней было, следовательно не понимает в каком состоянии база, и совпадает ли это состояние с кодом.
Там есть возможность перенакатить базу с форсом. Если это имеет смысл и локальный стейт не содержит нужных данных, или есть подготовленные скрипты наполнения базы случайными данными.
У мигрейта есть схожая функциональность. Можно указать на сколько шагов нужно откатить базу. Но при наложении миграций, или мерже «вчерашних» — он не сообщает о проблемах, что создает дополнительные риски при релизе.
НЛО прилетело и опубликовало эту надпись здесь
Вариант 1 — вторая ветка не имеет новых миграций для этой базы.
Миграцию выполнять не нужно. Проблемы нет.
Вариант 2 — во второй ветке есть невыполненная миграция. При ее выполнении будет создано новое состояние базы, которое не будет воспроизведено нигде. (результат мержа этих двух веток не всегда будет совпадать с результатом работы каждой из них отдельно выполненной в произвольном порядке)
Если обе меняют схему в БД — откатили и накатили другую.
Если нет — можно и так. По быстрому.
Если же по нормальному — нам нужно работать с тем же состоянием и не создавать себе проблемы с хранением в голове веток, их миграций, взаимосвязей в коде и в базе. Лучше эти ресурсы потратить на разработку самой фичи, и концентрацию на ней.
НЛО прилетело и опубликовало эту надпись здесь
Вы так говорите как будто каждая миграция выполняется по минуте.
Для сравнения — на довольно старом (ruby) проекте 100 миграций испольняются секунд за 7. Поэтому я сразу делаю себе алиас а баше, который дропает базу, потом создает ее, и накатывает все миграции. Все! Нет больше никаких страданий «а тут мы написали неправильный down для миграции и все рассыпалось». Проще накатить начисто при переключении ветки.
НЛО прилетело и опубликовало эту надпись здесь
Да. Поэтому у меня есть второй алиас. Он удаляет базу, создает заново, вливает туда дамп с прода (кторый лежит локально по известному пути), и докатывает миграции которые отсуствуют на проде. После этого можнон прогать.
Если говорить про прод, и конкретно про наш опыт в этом вопросе, то у нас такие кейсы вылавливаются либо при мердже feature ветки в основную, либо при накатке миграций на продовую базу. Как я упоминал ранее у нас есть отдельная deploy'ная job'а для накатки миграций и при ее работе сразу видно что накатилось, а что нет. Мы правда еще обычно и руками проверяем на всякий.

Если говорить про локальную машину — то в нашем случае если в разных ветках разные миграции и ребейзиться прямо вот сейчас не хочется, то никто не мешает убить контейнер с базой, а потом поднять его снова чистым и накатить миграции. Если посмотреть на docker-compose файл в статье, то мы делаем
make dev-down
make dev-server

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

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

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


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

А патчи git в этом случае не лучше подходят? не надо дополнительных remote создавать

А разве git remote add … && git fetch … не проще, чем создавать и распространять патчи?

так если есть шаблон, то и накатывать надо не один раз, а сразу на несколько сервисов

Это в теории. А на практике — у каждого микросервиса сейчас свои собственные задачи, и обновление "до последнего писка моды по шаблону" может не вписываться в его приоритеты. Равно как и возможности отложить все задачи и срочно обновить все микросервисы тоже может не быть. Поэтому — каждый микросервис обновляется в своём темпе и выборочно.

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


При этом подходе бинарник у нас один, но общий для всех встроенных микросервисов main.go минимальный, все микросервисы по-прежнему полностью изолированы друг от друга (у каждого своя БД, каждый раздаёт API на отдельном порту, код каждого в его собственном internal/, etc.) и, при реальной необходимости, довольно легко выносятся из монолита в отдельное репо. Тем не менее, это даёт реальную возможность "наводить порядок" сразу во всех микросервисах одним PR-ом, дешевле рефакторить внутренние API между этими микросервисами, и, в целом, заметно ускоряет разработку если у нас небольшая команда (когда 4 человека бегают между 50 репо с 50 микросервисами — это несколько утомляет).

У нас так. Но компания выросла, и теперь ты пол дня ждешь что бы замержить в мастер. Монорипо стало боттлнеком.

Выпиливаем сейчас в repository per domain. Пишем на ноде, но тут это не важно.

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

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

Один из реальных способов поддерживать общий стиль и единообразие между микросервисами — держать их в одном репо.

Это не про монорепо?

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

Не всё так страшно. Достаточно проконтролировать, чтобы встроенные микросервисы не использовали никаких глобальных объектов (вроде http.DefaultServeMux, prometheus.DefaultRegistrer и глобального хранилища миграций goose) плюс разрулить получение ими своих частей общей конфигурации (флаги/переменные окружения/etc монолита). Т.е. как только они стартанули (конфигурация, миграции) и если они не используют после этого никаких глобальных переменных — в целом всё будет гладко.


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


P.S. Монорепо != монолит. Я говорил про монолит. А монорепо (в котором разные не связанные проекты) я и сам готовить не умею пока — раздельное тестирование и выкат этих проектов на CI/CD в зависимости от того, какой код затронул текущий PR — это большая боль, по крайней мере во всех вариантах, которые я рассматривал.

А как вы решаете такие инфраструктурные вещи?

1. Service discovery
2. Graceful shutdown
3. Logging
4. Metrics

В нашей компании мы пишем на ноде. Но есть вещи которые работали бы намного эффективнее на GO. Но сейчас внедрить GO это проблема, потому сервисы на ноде основываются на обертке, которая и занимается теми вещами которые я перечислил. И что бы втиснуть в экосистему бинарник на GO, мне нужно будет обеспечить работу с консулом и т.д. А это уже выходит за рамки того что я как разработчик сервиса хочу делать. Обидно. А хочется просто кинуть бинарник, и что б работало =)
Наш генератор кода, который мы ласково зовем gogi генерирует по OpenAPI спецификации в том числе и серверный код, в котором есть код отвечающий за инициализацию логера, передачу метрик и т.д. То есть в вашем случае, возможно вам стоит начать с обертки которая будет заниматься всеми вещами что вы перечислили.

Насчет инфраструктурных вещей. Для метрик у нас применяется Prometheus, для логов у нас применяется Elastic+Kibana, Graceful shutdown можно сказать встроен в Kubernetes, трафик внутри Kubernetes мы направляем через ingress либо напрямую между сервисами.

Consul, насколько мне известно, мы сейчас не применяем.

Graceful shutdown лучше поддерживать из коробки внутри микросервисов. Потому что поддержка его докером (и, полагаю, кубом) сводится к тому, что он присылает сначала SIGTERM, а через 10 секунд SIGKILL. И вот неплохо бы на этот SIGTERM штатно отреагировать самому микросервису, аккуратно закрыв текущие подключения клиентов, остановив все фоновые процессы/горутины, и отключившись от используемых им самим БД/сервисов (иногда это прям критично, как в случае необходимости отключения от NATS Streaming, да и nats.Drain() неплохо бы сделать перед выходом).


Помимо этого полноценная поддержка graceful shutdown (в виде context.Context, который получают все работающие горутины, и который будет отменён в момент начала graceful shutdown) позволяет инициировать его изнутри, если какой-то "вечный" процесс внутри микросервиса внезапно завершится с ошибкой (напр. подключение к consul отвалится и не сможет восстановиться, а продолжать работать без него слишком опасно).

Конечно у нас в микросервисах есть обработка SIGTERM, высвобождение коннектов их повторная иницация в случае закрытия со стороны сервера и другие полезные вещи. Код для этого у нас тоже генерируется. Я думаю оригинальный вопрос был больше про то как погасить сервис перед этим перестав присылать на него запросы, чтобы они не падали в несуществующий upstream.
У меня следующие вопросы по списку:

1. Получается, что все сервисы шарят общий базовый код? Ведь в литературе о микросервисах мы повсеместно находим что-то вроде этого:
Microservices should adhere to a “shared nothing” approach where microservices do not possess shared code. Microservices should instead accept code redundancy and resist the urge to reuse code in order to avoid a close organizational link.

“Don’t repeat yourself” isn’t a golden rule Microservices allow you to violate the DRY (don’t repeat yourself) principle safely. Traditional software design recommends that you generalize repetitive code so that you don’t end up maintaining many copies of slightly different code. Microservice design is exactly the opposite: each microservice is allowed to go its own way


2. Если я ничего не упустил, у вас получается голый кубер, рассматривали ли какие-нибудь сервис меши для него? Чем закончились результаты рассмотрения, если были?
3. Вопрос авторизации и аутентификации в микросервисной архитектуре: долго ли мучались, на чем остановились?
4. Для чего вы используете в названиях пакетов несколько слов и underscores ?)
1. Получается, что все сервисы шарят общий базовый код? Ведь в литературе о микросервисах мы повсеместно находим что-то вроде этого:

В микросервисах написанных на python у нас была общая библиотека, которая шарилась между сервисами и мы на этом обожглись. Обслуживать эту библиотеку стало сложно, нужно постоянно следить за совместимостью версий, обновлять библиотеку в разных сервисах и при этом следить чтобы ничего не сломалось. Поэтому сейчас, в микросервисах на go у нас нет расшаренного кода. Весь код генерируется из OpenAPI спецификаций и сервисы независимы между собой. Шаблон микросервиса — это скорее история про эталон к которому должны подтягиваться проекты чтобы не сильно отличаться между собой. Он отрабатывает только на этапе генерации микросервиса и больше не является зависимостью. Можно сказать мы осознанно идем против принципа DRY здесь, чтобы не увеличивать связность между сервисами.
Если я ничего не упустил, у вас получается голый кубер, рассматривали ли какие-нибудь сервис меши для него? Чем закончились результаты рассмотрения, если были?

Очень хотим внедрить меши и скорее всего начнем какие-то движения в эту сторону уже в этом году. Насколько мне известно, наши DevOPS экспериментируют с Istio и Linkerd.
Вопрос авторизации и аутентификации в микросервисной архитектуре: долго ли мучались, на чем остановились?

У нас есть один сервис который занимается аутентификацией, к нему в основном обращаются 2 сервиса которые принимают трафик извне: наше мобильное API и сайт. Далее запросы от этих сервисов к микросервисам уже идут без повторной аутентификации. Авторизация при этом конечно остается. Мы обязательно проверяем, что пользователь запросил именно свои заказы, а не соседа) Насколько мне известно ощутимых болей при внедрении такого подхода никто особо не испытывал.
Для чего вы используете в названиях пакетов несколько слов и underscores ?)

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

подскажите, как решается вопрос с многофазной транзакцией в бд? Когда несколько микросервисов должны в транзакции записать/изменить данные, а потом при необходимости откатить их назад.

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

Данными, которые необходимо изменять в одной транзакции, должен владеть один сервис

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


Ну, и да — распределенные транзакции — это всегда больно, но мне казалось, что паттерны типа saga отчасти решают эту боль

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


Я всё понимаю про практику и теорию. Тем не менее, в большинстве случаев перепроектировать будет дешевле, чем внедрять саги.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий