Перейти до основного вмісту
Версія: 2.0

Побічні ефекти

Побічний ефект — замикання з недетермінованим кодом. Замикання виконується лише один раз, а результат зберігається. Якщо workflow виконується повторно, замикання не запускається знову, а повертає збережений результат. Це робить workflow детермінованим: відтворення завжди повертає те саме збережене значення без повторного виконання недетермінованого коду.

use function Workflow\V2\await;
use function Workflow\V2\sideEffect;
use Workflow\V2\Attributes\Signal;
use Workflow\V2\Workflow;

#[Signal('finish')]
class MyWorkflow extends Workflow
{
public function handle(): array
{
$token = sideEffect(fn () => random_int(1000, 9999));
$finish = await('finish');

return compact('token', 'finish');
}
}

Workflow викличе random_int() лише один раз і збереже результат, навіть якщо згодом зазнає збою та виконуватиметься повторно.

Коли використовувати побічні ефекти​

Використовуйте sideEffect(), якщо потрібне недетерміноване значення, яке:

  • обчислюється локально без зовнішнього введення-виведення: випадкові числа, UUID чи часові позначки
  • не має змінюватися після запису, навіть між відтвореннями
  • не потребує семантики повторних спроб, бо замикання виконується рівно один раз
// Generate a correlation token for downstream systems.
$correlationId = sideEffect(fn () => (string) Str::uuid());

// Snapshot the current time for a business rule.
$decidedAt = sideEffect(fn () => now()->toIso8601String());

Коли натомість використовувати activity​

Якщо код може зазнати збою, звертається до зовнішнього сервісу чи потребує семантики повторних спроб/тайм-ауту, використовуйте activity замість побічного ефекту:

СценарійВикористовуйте
Генерування випадкового токенаsideEffect()
Читання налаштування в момент рішенняsideEffect()
Виклик зовнішнього APIactivity()
Запис до бази данихactivity()
Надсилання листа чи сповіщенняactivity()
Дороге обчислення, що може викинути винятокactivity()

Практичне правило: якщо замикання може викинути виняток, після якого ви хотіли б повторити спробу, воно має бути в activity.

Принцип роботи​

  • кожен sideEffect() додає типізовану подію історії SideEffectRecorded з послідовністю кроку workflow
  • відтворення workflow та query повторно використовують зафіксоване значення без повторного запуску замикання
  • Waterline показує знімок побічного ефекту як типізований запис історії на часовій шкалі обраного запуску
  • побічні ефекти призначені лише для знімків, безпечних для відтворення, а не для роботи, що може зазнати збою чи потребує повторних спроб

Антишаблони​

Не викликайте зовнішні сервіси всередині побічного ефекту. Якщо виклик сервісу зазнає збою, побічний ефект не повторюватиметься, і workflow остаточно завершиться з помилкою:

// BAD: HTTP calls can fail and side effects do not retry.
$price = sideEffect(fn () => Http::get('/api/price')->json('amount'));

// GOOD: Use an activity for external calls.
$price = activity(FetchPriceActivity::class);

Не розміщуйте повільні чи блокувальні операції всередині побічного ефекту. Замикання працює в потоці завдання workflow. Тривала робота затримує все завдання workflow:

// BAD: Expensive computation blocks the workflow task.
$hash = sideEffect(fn () => bcrypt($largePayload));

// GOOD: Offload heavy work to an activity.
$hash = activity(ComputeHashActivity::class, $largePayload);

Не покладайтеся на змінний зовнішній стан. Замикання виконується рівно один раз. Якщо ви читаєте значення, що змінюється з часом, знімок фіксується в момент першого виконання, а не відтворення:

// The cached value is whatever it was during the first execution.
// If the cache changes later, this workflow still sees the old value.
$setting = sideEffect(fn () => cache('feature.flag'));

Це навмисна поведінка: знімок фіксується для детермінованості. Якщо потрібне значення, яке оновлюється протягом життя workflow, використовуйте signal чи activity.

Запуск цього шаблону​

Workflow вимірювання тривалості в Sample App — виконуваний приклад читання годинника через sideEffect():

php artisan app:elapsed

App\Workflows\Elapsed\ElapsedTimeWorkflow записує початкову й кінцеву часові позначки як цілі значення всередині callback sideEffect(), щоб значення зберігалося після декодування Avro під час відтворення. Деталі запуску Waterline показують дві події MarkerRecorded до й після спрацювання timer. Ця пара маркерів є збереженим на диску свідченням детермінованості читання годинника.