Забезпечення транзакційної консистенції з DDD


9

Я починаю з DDD і розумію, що сукупні корені використовуються для забезпечення транснаціональної узгодженості. Ми не повинні змінювати кілька агрегатів в одній службі додатків.

Мені хотілося б знати, як вирішити наступну ситуацію.

У мене є сукупний корінь під назвою Продукти.

Існує також сукупний корінь під назвою Group.

В обох є ідентифікатори, і їх можна редагувати самостійно.

Кілька продуктів можуть вказувати на одну групу.

У мене є служба додатків, яка може змінити групу товару:

ProductService.ChangeProductGroup(string productId, string groupId)

  1. Група перевірок існує
  2. Отримайте продукт із сховища
  3. Встановіть його групу
  4. Поверніть продукт назад у сховище

Також у мене є служба додатків, де можна видалити групу:

GroupService.DeleteGroup(string groupId) 1. Отримайте продукти з сховища, для якого groupId встановлено наданий groupId, переконайтеся, що кількість дорівнює 0 або скасувати. Видалити групу з сховища груп 3. Зберегти зміни

Моє запитання полягає в наступному сценарії, що буде, якщо:

У ProductService.ChangeProductGroup ми перевіряємо, чи існує група (вона є), а відразу після цієї перевірки окремий користувач видаляє productGroup (через іншу GroupService.DeleteGroup). У цьому випадку ми встановлюємо посилання на щойно видалений продукт?

Це недолік мого дизайну в тому, що я повинен використовувати інший дизайн домену (додаючи додаткові елементи, якщо потрібно), чи мені доведеться використовувати транзакції?

Відповіді:


2

У нас те саме питання.

І я не можу вирішити цю проблему, окрім транзакцій або з перевіреною послідовністю в БД, щоб отримати виняток у гіршому випадку.

Ви також можете використовувати песимістичний блокування, блокуючи сукупний корінь або лише його частини для інших клієнтів, поки бізнес-транзакція не буде завершена, що певною мірою еквівалентно серіалізаційній транзакції.

Шлях, який ви йдете сильно, залежить від вашої системи та ділової логіки.
Паралельність - це зовсім не легке завдання.
Навіть якщо ви зможете виявити проблему, як ви її вирішите? Просто скасуйте операцію або дозвольте користувачеві "об'єднати" зміни?

Ми використовуємо Entity Framework, а EF6 використовує read_commited_snapshot за замовчуванням, тому два послідовних читання з сховища можуть давати нам суперечливі дані. Ми просто пам’ятаємо про це на майбутнє, коли бізнес-процеси будуть більш чітко окреслені і ми зможемо прийняти рішення. І так, ми все ще перевіряємо узгодженість на рівні моделі, як і ви. Це принаймні дозволяє протестувати BL окремо від БД.

Я також пропоную вам продумати узгодженість сховища у випадку, якщо у вас є "довгі" ділові операції. Це досить хитро, як виявилося.


1

Я думаю, що це не специфічне питання щодо дизайну, керованого доменом, а питання управління ресурсами.

Ресурси просто повинні бути заблоковані в міру їх придбання .

Це вірно, коли служба додатків заздалегідь знає, що придбаний ресурс ( Groupсукупність, у вашому прикладі) буде змінено. У дизайні, керованому доменом, блокування ресурсів - це аспект, що належить до сховища.


Ні, блокування не є "обов'язковим". Насправді це дуже жахлива річ із тривалими транзакціями. Вам краще використовувати оптимістичну одночасність, яку підтримує кожен сучасний ОРМ. І відмінна річ в оптимістичній одночасності полягає в тому, що вона також працює з не транзакційними базами даних, якщо вони підтримують атомні оновлення.
Aaronaught

1
Я погоджуюся з недоліками блокування всередині тривалих транзакцій, але сценарій, описаний @ g18c, натякає на зовсім протилежне, тобто короткі операції над окремими агрегатами.
rucamzu

0

Чому ви не зберігаєте ідентифікатори продуктів у групі суб'єкта? Таким чином ви маєте справу лише з одним агрегатом, який полегшить роботу.

Тоді вам потрібно буде реалізувати певний тип схеми сумісності, наприклад, якщо ви виберете оптимістичну сумісність, просто додайте властивість версії до об'єкта групи та викиньте виняток, якщо версії не збігаються під час оновлення, тобто

Додайте продукт до групи

  1. Перевірте, чи існує продукт
  2. Отримати групу з сховища (включаючи його властивість id версії)
  3. Group.Add (ProductId)
  4. Збережіть групу, використовуючи сховище (оновіть новий ідентифікатор версії та додайте пункт де, щоб переконатися, що версія не змінилася.)

Багато ORM мають оптимістичну конкурентоспроможність, вбудовану за допомогою ідентифікаторів версій або часових позначок, але легко прокатати свої власні. Ось хороший пост про те, як це робиться в Nhibernate http://ayende.com/blog/3946/nhibernate-mapping-concurrency .


0

Хоча транзакції можуть допомогти, є інший спосіб, особливо якщо ваш сценарій обмежений лише кількома випадками.

Ви можете включити перевірки в запити, які роблять мутації, ефективно роблячи кожну пару перевірок і мутації атомною операцією.

Внутрішній запит на оновлення продукту приєднується до групи (щойно посилається). Це призводить до того, що він нічого не оновлює, якщо цієї групи немає.

Запит на видалення групи приєднується до будь-яких продуктів, що вказують на нього, і має додаткову умову, що (будь-який один стовпець) результат об'єднання повинен бути нульовим. Це змушує нічого не видаляти, якщо якісь продукти вказують на нього.

Запити повертають кількість рядків, які відповідали або були оновлені. Якщо результат 0, то ви втратили перегони і нічого не зробили. Він може кинути виняток, і шар програми може впоратися з тим, що він хоче, наприклад, повторивши спробу з самого початку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.