Обновить
59

Architect | Lead | Senior Developer

0,6
Рейтинг
13
Подписчики
Отправить сообщение

Это для вас он стал элементарным, в тот момент когда вы об этом узнали. А другим может никогда в голову не приходило вешать транзакцию на вложенный (приватный?) метод и у них все всегда работало.

У меня была подобная фигня однажды на собесе давным-давно. Вопрос был какой-то заковыристый про race condition, deadlock и все такое. У меня уже был некоторый опыт, но я не смог ответить. Потому что когда я строил свое решение (проект как раз был в банковской сфере, но не АБС) - я в первую очередь подумал про конфликты обработки данных, разграничил обработку порциями без пересечений и у меня не было проблем ни на запланированной нагрузке, ни на повышенной спустя некоторое время.

Это как раз те моменты, когда ответ на вопрос «я такой говнокод не пишу».

С другой стороны я лично наталкивался на различную другую дичь, но мне в голову не придет спрашивать про нее.

Например вы знаете, что mysql при определенной комбинации по разному обрабатывает блокировки на таблице с уникальным индексом? И зависит это от того - вызываете вы это внутри хранимки или без нее?

Я так же проверил mssql и postgresql, и там такой фигни нет.

Десятый — SELECT FOR UPDATE

Кстати довольно медленная штука, по крайне мере в MySql

https://habr.com/ru/articles/965812/comments/#comment_29113868

https://habr.com/ru/articles/955714/comments/#comment_28961570

А ему лопаты продает нвидия

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

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

Да, можно параллельно отработать со snapshota-ми, но потом они все равно все сойдутся в одной точке, с нормальной полноценной транзакционной блокировкой. В 99% случаев все будет хорошо, но вот на краю в 1% кому-то не достанется.

Так как во время обработки возможны проблемы с сетью, с подключением к БД, деадлоками - нужна retry policy. А это означает - очередь, с асинхронной обработкой, и три статуса для пользователя на UI - In processing | Approved | Failed (после допустим 5 попыток).

Если так проверять - то и купить будет нечего 😅

Так, это конверсия собесов в оферы, а какая конверсия откликов в собесы?

Да, я тот момент понял про пул потоков и возврат в исходный, просто тогда это означает то, что пересказывают в интернетах про ConfigureAwait и ASP.NET Core не совсем точная инфа. И вот дальше копать уже особо времени не было.

Интересно, сколько времени заняло все это выяснить?

В чистом веб-сервисе ConfigureAwait(false) ничего не меняет: поведение и так такое. ConfigureAwait(false) нужен в библиотечном коде, который может вызываться из WPF, WinForms или Xamarin, где SynchronizationContext есть. Если вы пишете обычный backend на ASP.NET Core, можно спокойно об этом не думать

И вот тут вот, у меня был кейс где помогло. Я глубоко не раскапывал как так вышло. Aspnet core приложение, Hangfire вызывает джобу, внутри джобы обращение к внешней системе с использованием Linq parallel.

Без этой настройки stop watch внутри каждого вызова показывали приемлемое время в сумме, типа 150-250мс * 10. Но общий stop watch на всю джобу выдавал значительно больше. Будто все висело где-то.

Добавил ConfigureAwait(false) и общий stop watch стал почти совпадать с суммой вложенных, что изначально и ожидалось.

Зубы жителя? 😅 /s

У меня детская травма, связанная с игрой Ну, погоди! Как вижу картинку - сразу ее вспоминаю, каждый раз.

Мне было лет 6, летели в ил-86, в Москву из Ташкента, и там раздавали эти электронные игры по салону, мы сидели после крыльев, папа сказал - иди сбегай, попроси игру, поиграешь, ну я побежал, но не успел.

Так и живем 🙃

Че то не очень понятно со string format - заменили на тормознутую конкатенацию, которую исправляли в другом месте и все залетало?

Ниче не понятно, хотя тема интересная. В статье нейрослоп перемешан с личным мнением, начинаешь прокручивать нейрослоп и теряется вообще вся суть. Можете по-нормальному переписать?

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

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

Ну а сейчас вообще оно ставиться как один большой Iframe (или даже как мини браузер) и просто тупо ведет на веб версию. Никакой апп стор не нужен и работает на полностью зарубежном айфоне в другой стране. Но что интересно - даже предлагает включить Face ID.

Тоже показалось, что банк, которым я пользуюсь - перешел на такую схему еще задолго до. Прям куски интерфейса менялись.

Кажется это очередная дичь в стиле "ИИ нас всех заменит". Почему не рассматривается вариант например, что народ начинает понимать, что ИТ сфера перенасытилась и просто перестают сюда идти?

С другой стороны - для олдовых сениоров это же хорошо. Все будут как 80-летние кобольщики и просить много денег.

Да и как бы 2031 год уже через 5 лет, бумеры даже еще работают, поколение X тоже, миллениалы тем более, массово на пенсию не собираются, помирать тем более.

Да, да, вы как бы больше боролись с графом зависимостей в NestJS и TypeScript. C# как будто бы в этом плане получше и проблема сложности перетекает на новый уровень.

И я еще забыл упомянуть один критерий в плане организации кода. Выделение каждого бизнес-метода в отдельный класс - снижает шанс на конфликты мержа в системах контроля версий. Это когда над проектом работают много людей и, допустим нескольким из них в спринте поставили задачи, которые затрагивают один большой файл - условный UserService. Чем мельче дробление, тем меньше шанс конфликта.

Вот это автора утилиты бомбануло 😅

Насколько я понял основная идея - это выделить каждый метод из service в handler и затем как из кирпичиков строить дом. И направление зависимостей. Но, скажем так, в C#, которым я пользуюсь, у меня никогда не было проблем с циклическими зависимостями. Но проблемы все равно остаются.

External service в итоге вырождается в тот же самый бывший service, только внутри не собственно бизнес код, а вызовы хендлеров.

С тем же успехом можно было сделать так

interafce ICreateUserHandler
{
  Result CreateUser();
}

interafce IGetUserByEmailHandler
{
  Result GetUserByEmail();
}

interafce IUserServiceExternal : ICreateUserHandler, IGetUserByEmailHandler
{
}

class UserService : IUserServiceExternal
{
  Result CreateUser()
  {
    ...
  }

  Result GetUserByEmail()
  {
    ...
  }
}

И в контроллер инжектить именно IUserServiceExternal, но не плодить 100500 классов хендлеров. То есть формат размещения кода особо не влияет.

Я то в своих проектах тоже пришел к этим условным handler, и так, чтобы их можно было собирать в цепочки еще, внутри оркестраторов или внутри друг друга (это кстати отражает BMPN / IDEF0). Но по результату могу сказать, что это особо не помогло побороть сложность.

Хендлер вроде бы один, но начинают появляться условные ветки, от которых код внутри хендлера должен вести себя немного по разному. Ну например когда создаем заказ из UI и из API, там немного разные правила. Начинаешь думать, что с этим делать. И там дилемма - либо делать два хендлера, где по началу 95% одинаковое (привет дублирование и любое изменение, которое нужно в двух хендлерах - нужно не забыть делать в двух хендлерах). Либо начинать мутить и выводить общий код в новый общий хендлер, либо делать что-то вроде шаблонных методов, или цепочек декораторов. И все снова скатывается в говнокод и кучу зависимостей.

И еще я все же ждал в статье блок, посвященный транзакциями. Там это реально проблема.

Вот пример недавней новой фичи.

Мы обрабатываем заказы в фоновом режиме. У нас есть хендлер, который качает их из внешней системы, там же обернуто в транзакцию и retry policy, причем как к внешней системе, так и в момент сохранения в БД. Далее у нас есть хендлер на обработку заказа, хендлер на отмену, хендлер на удаление. Каждый из них в транзакции и обернут в retry policy. Эти бизнес-процессы были сделаны ну допустим пару лет назад.

И вот приходит запрос сделать задержку в обработке заказов. Мы его должны скачать, сохранить, обработать частично (чтобы резерв товара прошел), но не отправлять на склад. И потом просто ждать. По истечении времени, например 4 часа - нам надо заново скачать заказ из внешней системы (потому что он мог изменится за 4 часа) и снова прогнать через процессинг. Но! По другим бизнес правилам, мы не можем дублировать заказы и нам надо как-то умудрится снять резерв, не потеряв его (потому что мы пообещали уже, что у нас есть товар под первую копию заказа).

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

1. Handler 1: Скачать новую копию заказа
  a) тут внимание, некоторые внешние системы требуют отправить подтверждение,
    что мы скачали заказ, но мы не можем это сейчас вызвать, потому что 
    мы еще не сохранили новую копию заказа, то есть нам надо вызывать этот 
    хендлер как бы частично
2. Handler 2: Отменить старую копию
  а) Handler 2-1: Отменить отгрузки (1 .. N) [у нас на UI есть кнопка 
     для отмены одной конкретной отгрузки, то есть это реально еще один 
     вложенный самостоятельный хендлдер]
  б) Handler 2-2: Отменить сам заказ [отменить заказ без отмены всех 
     вложенных отгрузок нельзя]
3. Handler 3: Создать заказ (он уже и так вызывается из трех мест - 
   UI, API, плагины/вебхуки)
4. Handler 4: Запустить обработку заказа, но без отправки отгрузкок на склад, 
   это фоновая операция, там у нас очередь [это еще один пример хендлера, 
   который тоже вызывается из 2-3 мест, но именно тут надо его вызывть 
   частично как бы]
5. Handler 5: На самом деле это продолжение Handler 1, потому что теперь 
   наконец-то мы можем подвердить внешней системе, что мы скачали новую 
   копию заказа 

И вот теперь представьте, что каждый хендлер тут имеет свою retry policy, этот оркестратор может упасть на ЛЮБОМ этапе, но нужно обеспечить повторяемость этой сложной операции, потому что, если мы потеряем заказ - клиент придет с жалобами и нам придется заплатить штраф.

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

Так то эта вся канитель существует уже давно, сколько я себя помню программистом. То есть найм был сломан всегда в этом плане. Просто раньше это решалось откликом на след вакансию. Сейчас вакансий мало, людей много. И обсуждать сломанный найм массово начали только в последний год-два.

1
23 ...

Информация

В рейтинге
2 339-й
Откуда
Россия
Зарегистрирован
Активность

Специализация

Бэкенд разработчик, Архитектор программного обеспечения
Старший
C#
.NET Core
SQL