Розподіл подій, одна подія, стан двох агрегатів змінилися


10

Я намагаюся вивчити способи DDD та суміжних предметів. Я придумав простий обмежений контекст для реалізації "банку": є рахунки, гроші можна здати на зберігання, зняти та перерахувати між ними. Також важливо зберігати історію змін.

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

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

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

Моє запитання - як я можу об'єднати ці три принципи: «одна сукупність однієї транзакції», «подія-> зміна сукупності» та «попередження одночасних змін»?

Відповіді:


7

При передачі це інше - два агрегати повинні бути змінені однією подією MoneyTransferred.

Переказ грошей - це окремий акт від оновлення книг.

MoneyTransferred
AccountCredited
AccountDebited

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

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

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

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

Я не зовсім впевнений, що випливає, тому що у вас є (для таких випадків) природний ідентифікатор кореляції, який є самим ідентифікатором транзакції.

Друга річ - це означає, що мені потрібно використовувати щось на кшталт саги.

Трохи інший правопис: вам потрібно щось на зразок людини, що розсилає правильні команди .

Є принаймні два способи, як ви могли це зробити. Одне було б мати абонента, який слухає MoneyTransferred, і відправляти дві команди в регістри.

Іншою альтернативою було б відслідковувати обробку транзакції як окремий сукупність - подумайте про це як контрольний список усіх речей, які потрібно виконати з моменту здійснення транзакції. Таким чином, MoneyTransferredобробник подій розсилає ProcessTransaction, який планує роботу, яку потрібно виконати, і перевіряє, яку роботу виконано.


Добре, але це означає, що я повинен використовувати події AccountCredited та AccountDebited також для депозитів та зняття коштів, тому я реєструю не причину змін, а зміну, спричинену деякими іншими діями. Якщо я хотів би змінити дію, я не зміг, оскільки не всі події реєструються. Як я можу це зробити (причинність подій)? Друга річ - це означає, що мені потрібно використовувати щось на кшталт саги. Як тоді слід моделювати передачу? Момент, коли у мене є метод переказу на рахунку. Коли його називають, він публікує подію MoneyTransferred . Я не знаю, з чого слід почати щось, наприклад, сагу.
cocsackie

Чи не так -> AccountCredited та AccoundDebited, тоді MoneyTransferred ? Перше рішення оновлює як наповнювачі в одній транзакції (без консистенції гарантії будь-якого роду)? Також немає агрегату, який міг би публікувати MoneyTransferred -> немає кореляції. Друге рішення здається кращим - ProcessTransaction може публікувати MoneyTransferred і уникати декількох сукупних модифікацій за одну транзакцію. Я можу публікувати події з Рахунку після здійснення транзакції. Вибачте за те, що вибагливий. Це для початківців важко зрозуміти - не можна використовувати лише один візерунок без іншого.
кокацькі

1

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

Маючи це на увазі, акт переказу грошей не повинен бути оновлений, accountа вставлений у transaction.

При цьому є ще одне важливе правило: акт додавання значка transactionмає бути атомним із оновленням до (денормалізованого поля балансу) account.

Тепер, якщо я розумію поняття DDD про агрегати, таке здається актуальним:

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

Тож з точки зору дизайну DDD я б запропонував:

  1. Є один агрегат, який представляє передачу

  2. Сукупність складається з таких об'єктів: передача (кореневий об’єкт); кореневий об’єкт пов'язаний з двома списками транзакцій (по одному для кожного облікового запису); і кожен список транзакцій пов'язаний з одним обліковим записом.

  3. Весь доступ до передачі повинен медитуватися кореневим об'єктом (the transfer).

Якщо ви намагаєтеся реалізувати підтримку асинхронної передачі, то ваш основний код повинен просто перейматися створенням передачі у статусі "очікує". У вас може бути інший потік або робота, яка фактично переміщує гроші (вставлення в історію транзакцій, а отже, оновлення залишків) і встановлює переказ на "розміщено".

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

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

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


0

Зараз я також перебуваю на етапі навчання. З точки зору реалізації, саме так я відчуваю, що ви виконаєте цю дію.

Dispatch TransferMoneyCommand, який піднімає наступні події [MoneyTransferEvent, AccountDebitedEvent]

Зауважте, перед тим як викликати ці події, потрібно буде виконати поверхневу перевірку команд та перевірку логіки домену, тобто чи достатньо балансу в обліковому записі?

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

Після успішного збереження подій у базі даних ви можете опублікувати дві події, які були порушені.

AccountDebitedEvent видалить гроші з рахунку платника (оновить сукупний стан та будь-які пов'язані з ними моделі перегляду / проекції)

MoneyTransferEvent запускає Saga / Process Manager.

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

Менеджер Saga / Process опублікує CreditAccountCommand, який застосовується до облікового запису одержувача, і в разі успіху, ніж AccountCreditedEvent, буде піднято.

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

Не соромтеся пропонувати будь-які питання або потенційні вдосконалення щодо вищезазначеного.

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