У попередньому матеріалі про DDD і межу узгодженості я писала, що одна бізнес-операція не означає одну велику транзакцію.
Наступне питання виникає природно: якщо частину роботи можна винести за межі транзакції, чи треба одразу робити Domain Event?
На практиці я не використовую події як стандартний спосіб викликати наступний крок. Спочатку визначаю, чи друга дія є частиною правильного стану, чи лише реакцією на вже завершений факт.
Схожий порядок дій може вимагати різного рішення
Кейс A активувати продукт | vзберегти необхідний стан Якщо другий крок не спрацює, продукт не має бути активним. Кейс B активувати продукт | vнадіслати сповіщенняУ першому випадку друга дія є частиною правильного стану системи. У другому вона лише реагує на вже успішну зміну.
Саме ця різниця для мене важливіша за те, наскільки красиво виглядає подієвий підхід.
Якщо друга дія є частиною інваріанта, я віддаю перевагу прямому виклику
DB::transaction(function () use ( $product, $registration,) { $product->activate(); $registration->confirmFor( $product->id, ); $this->products->save( $product, ); $this->registrations->save( $registration,Якщо Product не має права бути активним без підтвердженого обов’язкового стану, я не хочу спочатку зробити commit, а потім сподіватися, що listener успішно завершить другу половину правила.
Прямий виклик тут чесно показує залежність. Якщо один крок не проходить, транзакція відкотить обидва.
Подія не повинна послаблювати правило лише заради розділення коду
Якщо після першого commit система може опинитися у стані, який предметна область забороняє, event тут лише приховує проблему.
Код став менш прямим, але інваріант став слабшим. Саме такий випадок я розглядала в матеріалі про те, коли одна транзакція краща за event.
Domain Event підходить, коли система повідомляє про вже завершений факт
Активація продукту validate |change state |save |commit | vProductActivated reactions:- notification- analyticsProductActivated не просить когось завершити активацію. Вона повідомляє: активація вже успішно відбулася.
Якщо notification або analytics тимчасово не спрацюють, їх можна повторити, не скасовуючи сам факт активації.
Назва події допомагає перевірити її сенс
Факт: ProductActivatedAccessGrantedRegistrationConfirmed Назва, схожа на команду: ActivateProductGrantAccessConfirmRegistrationЦе не абсолютне правило, але корисний сигнал.
Якщо назва події звучить як команда "зроби це", можливо, я просто приховала прямий виклик за подією.
Доменна подія природніше читається як факт у минулому часі.
Реакція на факт повинна починатися після commit
DB::transaction(function () use ( $product,) { $product->activate(); $this->products->save( $product, ); DB::afterCommit(function () use ( $product, ) { ProductActivated::dispatch( $product->id, ); });Якщо подія означає "це вже сталося", я не хочу відправляти її до успішного commit.
Інакше listener може виконати зовнішню дію, а основна транзакція потім відкотиться. Система повідомить про факт, якого насправді немає.
Коли event справді додає користь
Подія для мене корисна, коли основний факт уже завершений, інша частина системи лише реагує, реакцію можна повторити, а джерело не повинно знати всі майбутні наслідки.
Саме цей тип сценарію я ширше розглядала в матеріалі про те, коли event є правильним вибором.
Чому я не використовую events everywhere
Method A |event 1 |listener |event 2 |listener |event 3 |listener Запитання:Подія створює непрямий зв’язок. Це її сила і водночас ціна.
Якщо для розуміння однієї дії потрібно знайти кілька listener і job, складність уже може походити не з предметної області, а з нашої організації коду.
Я використовую подію тоді, коли ця непрямість справді потрібна.
Прямий виклик методу не є автоматично поганою зв’язаністю
Якщо одна дія справді не може бути правильною без іншої, між ними вже є предметна залежність.
Event її не видаляє. Він лише робить її менш видимою.
У таких місцях прямий виклик часто чесніший: код одразу показує обов’язкову послідовність.
Application Service може явно координувати обов’язкові кроки
У матеріалі про Application Service і Domain Service я писала, що Application Service координує сценарій.
Якщо кілька кроків повинні відбутися в одній транзакції, явна координація тут часто читається краще за ланцюг прихованих listener.
Найкраще питання для мене: що повинно статися при помилці
Якщо другий крок не спрацює... Чи слід відкатити перший крок? ТАК-> прямий виклик-> та сама межа узгодженості-> зазвичай та сама транзакція НІ-> перший факт може зафіксувати-> другий крок може повторити спробу-> подія може відповідатиЦей тест для мене працює краще за абстрактне питання "event чи service?".
Якщо помилка другої дії робить першу недопустимою, я хочу rollback. Якщо перша дія залишається правильною, а другу можна повторити пізніше, event уже виглядає природно.
У AVN Center я перестала обирати event лише заради розділення коду
У роботі над AVN Center ця різниця стала для мене практичною.
Коли сценарії ставали складнішими, event іноді виглядав як найчистіший спосіб розділити відповідальності.
Але я поверталася до одного питання: друга дія є частиною правильного стану чи реакцією на вже правильний стан?
Це дозволило не розкидати критичні правила між listener і водночас не прив’язувати основну модель до побічних реакцій.
Моя коротка перевірка перед створенням Domain Event
Я ставлю такі питання:
- Чи основна дія вже є правильною без listener?
- Чи друга дія лише реагує на факт, який уже стався?
- Якщо listener впаде, чи можна безпечно повторити його пізніше?
- Чи помилка другої дії повинна rollback першу?
- Чи event описує факт, а не приховану команду?
- Чи подія реально зменшує небажану зв’язаність, а не приховує обов’язкову послідовність?
Я обираю за типом залежності, а не за модністю підходу
Для мене прямий виклик методу і Domain Event більше не є конкурентами.
Прямий виклик добре показує обов’язкову залежність: без другого кроку перший результат не є правильним.
Доменна подія добре показує завершений факт: основна зміна вже правильна, а інші частини системи можуть на неї реагувати.
Якщо друга дія захищає інваріант, я залишаю її в основному сценарії. Якщо вона лише реагує на факт після commit, тоді event стає природним вибором.
Event не повинен приховувати обов’язкову залежність. Якщо без другої дії перша стає неправильною, це одна узгоджена операція, а не реакція після факту.



