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

Пользователь

Отправить сообщение

Так а если часть не сохранится, какая разница transaction script у нас или нет, неконситетность будет в базе же?

А работает ли?

Конечно, нет select же в транзакции - нет проблем. Ну и никто даже теоретически пример не смог придумать, почему не сработает и в какой ситуации

Да, внешнее api, я понял, в use case у вас транзакция. С несколькими сохранениями на use case понятно, но тоже нарушает принцип 1 транзакция на запрос ну или всеми нелюбимые саги нужны, чтобы либо все сохранения прошли, либо ни одного

При вышеупомянутой ошибке транзакции всё повторяет

Страдать не приходится, когда I/o есть в use case update-а? Его же нужно как-то вне транзакции разместить, но всё ещё в use case?

чем анализировать требуемый уровень изоляции для каждого use case

Как раз необходимость анализировать каждый кейс при UoW пока не доказана, у нас во вселенной с ORM у всех все работает без этих анализов, зато с оптимистическими блокировками и обновлениями только изменившихся полей моделей из коробки и не тормозит ничего, переходите на темную сторону)

Там - это при использовании DDD с Serializable транзакцией на весь use case или в Transaction Script? :-)

Там - это без повторения всего use case и даже без изменения модели в памяти (как я процитировал ранее), оба примера модель меняют и весь кейс повторяют

потребоваться дополнительные хаки

в DDD как раз рекомендуют не создавать агрегаты из воздуха, а чтобы один создавал другой, так что всё по канонам)

Но вот что произойдёт если бизнес-логика обновления агрегата будет зависеть от выборки группы каких-то других записей из БД

Так это и есть инвариант (наш предыдущей топик) - всё должно быть в одном агрегате

10 зарегистрированных пользователей получают статус "early adopter"

Например, будет некий агрегат RegistrationManager с единственным полем userCount и оптимистической блокировкой - двое поменяют c 9 на 10, она сработает и только первому даст. Ну и, кстати, встречный вопрос - а как там это работает в этом же кейсе при конфликте:

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

Там ведь нельзя будет того же нового user-а сохранить, нужно будет вычитать заново, что их уже 10 и поменять тип в user, или как?

Так везде, где не используется UoW

Лучше уж везде UoW использовать, чем Serializable ставить или каждый use-case анализировать, чтобы нужный уровень выбрать, нет?

Вот конкретный пример, как раз с учётом ORM:

Больше всего тоже смущает (если я правильно понял), что транзакцию открывают уже на SELECT-е, не понятно, с какой целью

Сам по себе DDD-шный подход "начать транзакцию, считать весь агрегат, изменить, сохранить весь агрегат, закоммитить транзакцию"

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

теория ясна, пример бы конкретного use case-а, когда это важно, можно будет прикинуть как это решает ORM

не встречал кейсы, где read committed + стандартная работа через orm приводила к проблемам и нужно каждый use case проверять, не приведёте пример?

не, на такое желание смотреть пропало) 640 кб read committed должно хватать всем, что-то уж больно специфичное у Вас, не классический энтерпрайз

"короткие" транзакции с намного меньшим шансом на конфликт; возможность автоматически и безопасно повторять отдельные транзакции (но не весь use case) при конфликтах (потому что внутри транзакции никто точно не полезет дёргать внешние API с непонятными побочными эффектами и т.п.)

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

А как именно Вы это "делить по другому" и при этом "не укрупнять" себе представляете?

Возможно я понял, в чём у нас недопонимание: я имею ввиду делить по другому, чтобы исключить сагу, не когда уже мы выделили агрегаты из критерия транзакций, а когда:

за агрегаты мы берём не те сущности, которые есть в бизнесе, а которые нам привычны

Т.е. если, например, мы имеем сущности A, B, C и мы решили, что пусть будет два агрегата AB и C, потому что так привычно, а потом оказалось, что нужна транзакция между B и C, то вместо саги, нужно в агрегат объединить как A и BC (т.к. мы не делили изначально по транзакциям, то A и B могли быть вообще не связаны транзакционностью или бизнесу допустима eventual consistency между ними). Если изначально нужна транзакция между AB, а потом ещё и появилось, что нужно между B и C, то понятно, что только укрупнение транзакции/агрегата в ABC или сага.

Нельзя же используя часть данных другого агрегата создать ещё один агрегат, более "удобный" для конкретного use case.

Нет, это про другое, в контексте выделений агрегата по привычке: вот я говорил про User, вместо Buyer, Reporter: представим два use-case 1) создать Order с констрейнтом "не может быть больше 1 активного заказа на User" 2) при проблемах по заказу отправить Ticket в поддержку (констрейнт "1 активный Ticket на одного User-а"). Если бы мы имели один агрегат User, то нам пришлось бы в него объединять и Order-ы и Ticket-ы (пример условный, сейчас за скобками, почему это всё в одном BC). Если же это Buyer и Reporter, то у каждого было бы по своему списку. Если рассматривать это как пример выше с ABC, где User мог бы быть B, а Buyer и Reporter могли бы быть B1 и B2. Тогда в случае с User-м у нас было бы выход только укрупнение до ABC, а в случае с Buyer и Reporter осталось бы AB1 и CB2.

Я в целом, думаю лучше понял статью (спасибо за ответы), понятно, что в случае Transaction Script (ну или если не накладывать таких жёстких ограничений 1 транзакция - 1 агрегат - см. Single transaction across aggregates versus eventual consistency across aggregates NET-Microservices-Architecture-for-Containerized-NET-Applications), саги не нужны будут. По крайней мере если речь идёт о простых случаях, когда саги могли бы быть из-за инвариантов, а не из-за взаимодействий с внешними сервисами.

Звучит как Transaction Script vs Domain Model внутри Bounded Context. Да это бесспорно, что если получилось так разделить на микросервисы, что между ними саг нет, и внутри Transaction Script вывозит бизнес-логику - здорово, так и нужно делать. Но к сожалению это не всегда так, и есть ситуации когда Domain Model внутри Bounded Context даёт больше, чем проблем приносит (1-2 саги можно и реализовать на 50 других use-case-в)

 Предложенное Вами дополнение к правилам определения границ агрегата - не является официально рекомендованным лидерами DDD

Повторю сказанное выше: никаких дополнений я не предлагал и не предлагал укрупнять агрегаты при возникновении use-case-в требующих саг (делить по другому, создавать новые агрегаты под разные use-case вместо использования одного агрегата - да, но не именно укрупнять)

 И, нет, фраза "по границе транзакции" подразумевает несколько другое и не противоречит использованию саг.

К совместимостью саг с этим тоже вопросов не было, тут мы про одно, думаю, что лучше стремить их минимуму (желательно 0). А вот про то, что подразумевает фраза "по границе транзакции", я не понял, вы же вроде ранее согласились, что:

Разумеется, основной критерий - это транзакции. Но, как Вы же сами и заметили, тут речь исключительно о "transactional consistency".

или нужны ещё цитаты ссылки? Я вроде только это и утверждал

Ну т.е. иными словами вы предлагаете дополнить правила формирования границ агрегата ещё одним

Нет, своего я не предлагал ничего, я про то основное правило - агрегат = инвариант (в терминах, приведённых ранее). Я тут не про те кейсы, когда бизнес допускает согласование с задержкой, но требует саги (отката) (к сожалению и счастью такого не встречал и что-то не могу придумать), я больше про то, что скорей всего бизнес как раз не устраивает с задержкой (например, с UI команда пришла, в которой нужно два агрегата поменять или ни одного и вернуть ответ/ошибку) и вместо того, чтобы пересмотреть границ агрегатов под новые реалии люди лепят сагу.

Встречал кейсы, когда допускается согласование с задержкой, но там не откат, а именно компенсаторное действие и оно отлично без явных саг тоже решается (user добавил товары в корзину, заказал, при сборке заказа обнаружилось, что нет на складе - склад шлёт заказу событие, заказ переводится в состояние, требующее реакции user-а, user может согласиться и опять отправить на склад). В таких примерах нет ничего сложного и нет, никто не предлагает из-за наличия такого use-case-а объединять склад и заказ в один агрегат (не факт даже, что на складе товара нет по причинам concurrency между разными агрегатами, мог просто потеряться/испортится товар).

Для этого в таких проектах граница транзакции проходит по микросервису (Bounded Context), а не агрегату.

Представим простую ситуацию, без взаимодействия с внешними сервисами. В bounded context три агрегата - A, B и C. Мы их проектируем согласно границам транзакций, и большинство транзакций меняют только один из этих агрегатов. Но потом появляется use-case, который требует соблюдение инварианта между B и С - согласно рекомендациям из этой статьи мы себя не ограничиваем и создаём транзакцию на use-case вместо саги, т.к. B и C в одном микросервисе/bounded context-е. Но если это так, то как контролируется/ограничивается, что какой-то другой use-case над B или C не нарушит инвариант между B и C?

А где я такое предлагал? 

Тут:

В DDD практически всё прекрасно, за исключением одного тактического паттерна: определения границы транзакций по агрегату. (К сожалению, это ключевой элемент всей “тактической” части DDD. Так что избежать этого паттерна можно только если ограничиться в своём проекте применением “стратегии DDD”, полностью отказавшись от “тактики DDD”.)

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

Саги появляются там, где бизнес допускает согласование с некоторой задержкой.

Не придирки ради: тут может быть два варианта - 1 - когда другие агрегаты просто принимают как данность произошедшее в третьем и меняют своё состояние, 2 - когда другие агрегаты тоже проверяют свои инварианты и при их несоблюдении требуется откатить произошедшее в третьем. Я так понимаю мы обсуждаем (2).

И избавиться от саг в этих случаях можно только избыточно раздувая агрегат

Этого (случая 2) позволяет избежать как раз проектирование границ агрегатов от инвариантов в use-case-х. Раздувание происходит как раз когда за агрегаты мы берём не те сущности, которые есть в бизнесе, а которые нам привычны (у нас может появляться User, тогда как вместо него могли быть несколько Buyer, Reporter; Product, вместо BusketItem, InventoryItem - т.е. один из подходов - разделяем сущности по use-case-м так, чтобы use-case укладывался в транзакцию над агрегатом) Да, иногда возникает ситуация, что так, как мы выделили границы агрегатов у нас только 95% операций выполняются транзакционно (с 1 агрегатом на транзакцию), но те 5% нет никаких проблем корректно реализовать сагами (опять же, вместе с бизнесом, обсуждая каждую деталь, что должно быть, когда такси недоступно, а билет на самолёт и в отель мы купили, можем ли мы откатить покупку билета или нужно уточнить у пользователя. И DDD тут только помогает такие кейсы вычленить и на них сфокусироваться с бизнесом).

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

1

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность