0% прочитано

Domain Event чи звичайний виклик методу: як я обираю між ними

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

14 жовтня 2026 р. 10 хв читанняЛаравель

У попередньому матеріалі про 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- analytics

ProductActivated не просить когось завершити активацію. Вона повідомляє: активація вже успішно відбулася.

Якщо notification або analytics тимчасово не спрацюють, їх можна повторити, не скасовуючи сам факт активації.

Назва події допомагає перевірити її сенс

Подія природніше описує факт, який уже стався
Факт: ProductActivatedAccessGrantedRegistrationConfirmed  Назва, схожа на команду: ActivateProductGrantAccessConfirmRegistration

Це не абсолютне правило, але корисний сигнал.

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

Доменна подія природніше читається як факт у минулому часі.

Реакція на факт повинна починатися після commit

Listener не реагує на факт, який ще може rollback
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 не повинен приховувати обов’язкову залежність. Якщо без другої дії перша стає неправильною, це одна узгоджена операція, а не реакція після факту.