Стас Выщепан @gandjustas
Оптимизирую программы
Информация
- В рейтинге
- 221-й
- Откуда
- Москва, Москва и Московская обл., Россия
- Дата рождения
- Зарегистрирован
- Активность
Специализация
Архитектор программного обеспечения, Деливери-менеджер
Ведущий
C#
.NET Core
Entity framework
ASP.NET
Базы данных
Высоконагруженные системы
Проектирование архитектуры приложений
Git
PostgreSQL
Docker
Что за бред вы несете?
Лог операций это не эвентсорсинг. Лог операций при строгой консистентности изменений применяли за много (тысяч?) лет до изобретения эвентсорсинга.
Точно также, но есть нюанс, который вы как обычно игнорируете.
Пользователь видит не логи, а суммую. Сумма получается через неопредлеленное время после записи в лог.
У вам обязательно случится ситуация, когда вы записали в лог -1, но сумма не успела обновиться, а в это время другой пользователь сделал действие (оплатил товар) и в лог еще раз записали -1. Сумма пересчиталась и стала отрицательной, а такого случаться не должно.
Вы осознаете что такая проблема существует? Я что-то уже начал сомневаться в этом.
Когда вы используете строгую консистентность у вас сумма перечситывается сразу и в минус она никогда не уйдет.
Пример с твиттером это пример с данными которые можно потерять. Лайкам и репостам не требуется вообще никакая консистентность. Если ваш лайк никто не увидит, то вы об этом не узнаете.
Да вы что?
Списывать (резервировать) единицу товара надо в момент оплаты. Иначе до физического выбытия вы её ещё раз продадите.
Как это можно сделать в es?
Вы подменяете понятия.
Лог изменения вполне может существовать без эвентсорсинга. Неблокирующие рид-модели тоже могут существовать без эвентсорсинга, обычно это называется кэш. А в случае большой нагрузки на запись кэш может обновляться асинхронно.
Ключевая особенность эвентсорсинга это безусловная "отложенная согласованность" она же "согласованность в конечном счете". Как я уже приводил многократно примеры эта "отложенная согласованность" с большим успехом позволяет терять данные и приводит к ошибкам.
Вы сами говорите что всегда надо знать сколько товара на складе. Теперь объясните как гарантированно не продать больше чем есть на складе при отложенной согласованности.
Тут как анекдоте про нюанс. Он всегда есть.
Если у вас строгая косистентность, то вы можете рассчитывать на то, что после записи в базу факта звонка вы можете получить счет.
Если у вас "консистентность в конечном счете", то не можете. Вы должна подождать пока некоторый фоновый процесс сделает вам счет. Сколько ждать - в общем случае неизвестно. Если за время ожидания поступят еще звонки, то полученный в любой момент счет будет неактуальным.
И такой подход на полном серьезе предлагается, как имеющий некоторые преимущества перед обычной консистентностью.
Вы думаете в 1с какая-то магия происходит? Там такие же запросы в базу и тоже часто встречается кривой код конфигурации, который может данные потерять.
В реальном мире транзакции существуют. Любая сделка это транзакция. Обязательства возникают одновременно у множества контрагентов.
Более того, любое взаимодействие двух и более субъектов это транзакция. Мы нажимаем кнопку "отправить" и хабр размещает наш ответ под постом, видимых другим участникам. Это происходит атомарно: нажали - получили результат.
В компьютере изначально транзакций нет. Вы меняете одну ячейку памяти, а в это время другой поток меняет другую. Для создания транзакционности применяются разные алгоритмы на основе атомарных операций, они слава богу в компьютеры встроены.
Один из таких алгоритмов, который применяется практически во всех СУБД, это Write Ahead Log (WAL). Мы сначала атомарной операцией записи сохраняем то, что мы хотим поменять, а потом уже меняем и если все ок, то помечаем данные меткой этой транзакции, а если нет, то транзакцию откатываем - просто не сохраняем изменения.
У вас уже есть WAL. Даже если нет и вы нашли систему без WAL, вы его изобретаете в виде eventstore.
Как можно оплату с задержкой принять? Пользователь сформировал заказ, нажал оплатить, дальше что?
Я посмотрел ваш код, у вас там тест пинает многократно апи, пока заказ не станет в нужном состоянии. Но в реальности это не бездушная машина делает, а пользователь нажимает кнопки на сайте. Если он увидит, что после добавления товара в корзину количество не поменялось он уйдет с вашего сайта и никогда не вернется.
А если вы внутри фронтэнда повесите это пинание многократное, то для конечного пользователя это будет выглядеть как дикие тормоза по непонятной причине.
ACID и сохраняемые транзакции это одно и то же. Не бывает транзакций без ACID, и не бывает ACID без транзакций. Это определяющие свойства.
Что значит "становится хрупким" ? Почему имеет тенденцию разрастаться? И почему на это должны влиять проекции? Можете привести пример кода?
Мой пример без эвенсорсинга:
Даже если вы можете работать с EF7 или не приучены писать ExecuteUpdate, то можно написать такой код:
Если кто-то межу строками 3 и 5 поменяет статус заказа, то saveChanges отвалится из-за оптимистичной конкуренции и оплата не пройдет.
Что в этом примере "становится хрупким" ? Что будет разрастаться?
Как вы решите проблему двойной оплаты в своем коде?
Я вижу проблемы в вашем коде. Я вижу что если перевести код на простые чтение и запись базы, то проблем станет меньше. Поэтому и прошу привести более жизнеспособный пример, чем заказы и оплаты.
PS. Открыл ваше резюме и увидел, что вы занимались payment gateway в Додо. Там все так плохо как у вас в статье?
Мой тезис в том, что когда вопрос касается денег или вообще любых внешних воздействий - отправки почты например, лучше априори считать что ущерб от нетранзакционности большой и случаться всякая фигня будет часто.
Пока не доказано обратное.
У вас два неявных предположения:
Вы предполагаете что проблемы в отсутствии транзакционности будут возникать редко.
Вы предполагаете что поддержание ACID гарантий сильно замедляет скорость разработки.
Оба предположения неверные.
В моей практике был один интересный случай. Я был тогда еще очень молод, только закончил универ, где на предпоследнем курсе мы изучали реляционные СУБД. К нам в контору устроился весьма опытный программист, много чего написал в своей жизни и умел реально быстро делать.
И как-то раз мы с товарищем, с которым учились вместе, посмотрели его код. Там было так: запрос "Получить значение А", небольшие вычисления со значением А, которые занимали едины миллисекунд, запрос "Записать в А новое значение". Запросы были не в транзакции.
Мы с товарищем сразу почувствовали неладное. Но опытный программист сказал нам: "там задержка между запросами минимальна, не успеет другой запрос туда вклиниться, я всегда так делал. Если написать транзакцию, то будут дедлоки, а так проблем никогда не было".
Меньше чем через две недели отдел разработки завалили жалобами на некорректное поведение программы. Потом он признался, что подавляюще большинство его программ были однопользовательскими, то есть параллельно с ними никто не работал. А один раз он сталкивался с подобной проблемой, но когда добавил транзакцию начал ловить дедлоки и просто продавил заказчика не выполнять операцию несколькими пользователями параллельно.
В итоге в код все равно были добавлены транзакции и updlock. Но если бы это было сделано сразу, то не было бы ущерба репутации и не было бы потрачено несколько часов работы саппорта.
Этот случай не доказывает что проблемы всегда будут происходить часто и ущерб от них будет большой. Но этот случай вполне говорит нам, что вы не можете заранее угадать как часто будут проявляться проблемы и какой будет ущерб от нетранзакционности. Даже если у вас большой опыт.
И если они будут проявляться часто и ущерб будет достаточно большой, то вам все равно придется делать транзакции. Поэтому для любых данных, которые клиент не хотел бы потерять, нужна транзакционность.
Платежный сервис он не ваш, это robocassa или что-то в этом роде. Он работает просто: вы передаете запрос, он показывает пользователю страницу, берет денные карты клиента, списывает деньги, делает веб-запрос (вполне себе идемпотентно) по указанному вами адресу, а клиента перенаправляет назад в ваш магазин.
Но даже если представить что сервис ваш, то это никак не помешает вам дважды принять оплату в эвент-сорсинг архитектуре.
Чтобы делать пессимистичные и дюрабельные блокировки вам нужна согласованность write и read моделей (внезапно!)
Как идемпотентность вызовов поможет вам не потерять данные об оплате клиента в случае eventual consistency?
Ваш пример в статье такой, что если переписать его без эвенсорсинга на обычную РСУБД, то он станет работать не просто надежнее, но и быстрее. А аудит можно прикрутить отдельно.
Есть у вас пример, который не получится переписать без эвенсорсинга без потери ключевых характеристик?
Именно поэтому я спросил про пример с полезными свойствами, так как все что мне приходит на ум - решается без эвентсорсинга гораздо лучше, чем с ним.
Дочитал до пункта "когда не применять"
Когда у вас появляются деньги вам нужна строгая согласованность.
В вашем примере вы легко можете взять с клиента оплату за заказ дважды:
Клиент жмет "оплатить" на странице заказа, в момент проведения транзакции связь ломается, клиент не получает ответа от платежного сервиса, но информация об оплате попадает в эвентстор.
Далее клиент перезагружает страницу заказа, видит что статус заказа все еще "ожидает оплаты", так как трансформер не отработал, и оплачивает еще раз.
Вы потом долго разбираетесь в эвентсторе откуда два раза изменение статуса с "не оплачен" на "оплачен" (кстати что делать если в эвентсторе оказались несовместимые события, как разруливается конкурентный доступ?)
Подобный сценарий "фантомного чтения" легко обобщается на любое взаимодействие с внешним миром, даже отправку смс или емейл. И даже на некоторые сценарии работы исключительно внутри вашего приложения.
Может быть у вас есть пример эвентсорсинга, который несет только полезные свойства, а не tradeoff с отрицательной суммой?
В википедии инфа актуальная
https://ru.wikipedia.org/wiki/Ковариантность_и_контравариантность_(программирование)
Вариантность относится к обобщенным типам
Там у вас неверное употребление понятия вариантности.
"Не ломай публичный контракт" это LSP.
OCP это "проектируй классы так, чтобы наследники не смогли сломать публичный контракт и инварианты класса"
По сути две стороны одной медали
Претенциозное название статьи, а по факту просто переименовали тестеров в разработчиков.
Потому что из раза в раз повторяют очевидные, но от этого не менее полезные вещи.
Он не может оплачиваемую работу найти?
А если вместо pub\sub скажет что это Observer? Или скажет что надо использовать список коллбеков или events?