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

Saga

Цей посібник описує компенсацію у вбудованих Laravel workflow з Workflow\V2\Workflow. Для worker у сервісному режимі використовуйте посібник PHP SDK, посібник Python SDK або довідник Rust SDK.

Saga — це усталений шаблон проєктування для керування складними тривалими операціями:

  • Saga керує розподіленими транзакціями через послідовність локальних транзакцій.
  • Локальна транзакція — це одиниця роботи, яку виконує учасник saga, тобто activity.
  • Кожну операцію saga можна скасувати компенсувальною activity.
  • Шаблон saga передбачає, що всі операції або успішно завершуються, або відповідні компенсувальні activity виконуються для скасування вже завершеної роботи.
use function Workflow\V2\activity;
use Workflow\V2\Attributes\Type;
use Workflow\V2\Workflow;

#[Type('booking-saga')]
class BookingSagaWorkflow extends Workflow
{
public function handle(): array
{
try {
$flightId = activity(BookFlightActivity::class);
$this->addCompensation(fn () => activity(CancelFlightActivity::class, $flightId));

$hotelId = activity(BookHotelActivity::class);
$this->addCompensation(fn () => activity(CancelHotelActivity::class, $hotelId));

$carId = activity(BookRentalCarActivity::class);
$this->addCompensation(fn () => activity(CancelRentalCarActivity::class, $carId));

return compact('flightId', 'hotelId', 'carId');
} catch (\Throwable $e) {
$this->compensate();

throw $e;
}
}
}

Коли workflow перехоплює виняток, $this->compensate() виконує всі зареєстровані компенсації у зворотному порядку. У наведеному прикладі, якщо BookRentalCarActivity завершується помилкою, рушій спочатку скасовує готель, а потім переліт, розгортаючи saga назад від найновішого кроку.

Реєструйте кожну компенсацію після успіху її основної activity. Replay відновлює цей порядок реєстрації й використовує записані результати activity, зокрема завершених компенсацій.

Порядок компенсацій​

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

Типово compensate() зупиняється на першій помилці компенсації й передає її коду, який викликав цей метод.

Паралельна компенсація​

Для паралельного виконання компенсацій використовуйте setParallelCompensation(true). Коли паралельну компенсацію увімкнено, кожне замикання компенсації має повертати розпочатий виклик activity без очікування його результату, щоб рушій міг виконувати їх конкурентно:

use function Workflow\V2\activity;

use Workflow\V2\Attributes\Type;
use Workflow\V2\Workflow;

#[Type('parallel-saga')]
class ParallelSagaWorkflow extends Workflow
{
public function handle(): void
{
$this->setParallelCompensation(true);

try {
$flightId = activity(BookFlightActivity::class);
$this->addCompensation(fn () => activity(CancelFlightActivity::class, $flightId));

$hotelId = activity(BookHotelActivity::class);
$this->addCompensation(fn () => activity(CancelHotelActivity::class, $hotelId));

activity(ChargePaymentActivity::class);
} catch (\Throwable $e) {
$this->compensate();

throw $e;
}
}
}

Коли паралельну компенсацію увімкнено, замикання компенсацій повертають виклики activity, які рушій збирає й виконує через all().

Продовження після помилки​

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

$this->setContinueWithError(true);

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

Поєднання обох прапорців​

setParallelCompensation(true) та setContinueWithError(true) можна використовувати разом. Тоді всі компенсації виконуються конкурентно через all(), а помилка будь-якої компенсації перехоплюється, щоб решта все одно завершилися. Без setContinueWithError(true) помилка паралельної компенсації одразу передається далі, і workflow завершується помилкою.

$this->setParallelCompensation(true);
$this->setContinueWithError(true);

Як це працює​

  • addCompensation() реєструє функцію, яка буде викликана під час compensate()
  • compensate() перебирає зареєстровані компенсації у зворотному порядку
  • кожне замикання компенсації є звичайним кроком workflow V2. Викликані ним activity створюють стійкі події історії, як і будь-яка інша activity
  • компенсувальні activity видно у хронології Waterline та експорті історії
  • якщо workflow завершується успішно, замикання компенсацій не викликаються й не створюють історії

Запуск цього прикладу​

Saga туристичного агента, керована signal, у Sample App — це виконуваний приклад для цієї сторінки. Клонуйте sample app, задайте OPENAI_API_KEY і виконайте:

php artisan app:ai

App\Workflows\Ai\AiWorkflow реєструє компенсації для кожної activity бронювання. Тому помилка перельоту після успішного бронювання готелю скасовує готель через список компенсацій. Це той самий шаблон, описаний на цій сторінці, з подіями у хронології запуску Waterline.