Что такое Temporal?

Что такое Temporal?

Temporal - это платформа оркестрации микросервисов и движок бизнес процессов.

Temporal предоставляет разработчикам набор инструментов для описания бизнес процессов кодом и гарантирует надёжное выполнение этого кода, даже при наличии ненадёжных внешних сервисов.

В случае ошибок, Temporal автоматически перезапускает приложение, причём уже исполненные инструкции не будут повторены, и исполнению начнётся ровно с того места, где в прошлый раз возникла ошибка. Этот же механизм позволяет Temporal автоматически обрабатывать ошибки и повторять неудачные запросы.

Можно сказать, что Temporal позволяет писать код, как будто ошибок не существует.

Разбираемся на примере

Далее пойдёт вольный пересказ выступления одного из создателей Temporal, Максима, из этого видео

Раз уж заговорили про транзакции, давайте посмотрим на классический пример - перевод денег.

Чтобы перевести деньги, нужно сделать 2 шага:

  1. Вычесть деньги с одного счёта
  2. Прибавить деньги на другой счёт

Если бы это можно было сделать только с помощью базы, механизм транзакций любой ACID базы данных защитил бы нашу систему от несогласованного состояния и мы могли бы быть уверенны, что запрос не повиснет в промежуточном состоянии - когда с одного счёта мы сняли, а на другой не перевели.

Но если мы хотим построить масштабируемую систему, которую реально поддерживать, писать бизнес логику внутри базы на SQL - очень сомнительная идея.

В реальном мире, операциями с деньгами занимается отдельный (микро)сервис, который предоставит нам пару методов для подобных операций: один для снятия (допустим, withdraw), другой для зачисления (допустим, deposit). А мы бы из своего сервиса эти методы дёргали по необходимости.

Happy flow

Например, в самом простом и наивном варианте такую функцию перевода денег на PHP можно написать так:

public function transfer(
    Account $from,
    Account $to,
    int $amount
): void {
    $this->moneyService->withdraw($from, $amount);
    $this->moneyService->deposit($to, $amount);
}

Где $this->moneyService - это обёртка для работы с сервисом денежных операций.

“Наивным” это решение я называю потому что оно учитывает только счастливый сценарий. Если всё всегда будет работать как надо и ничего не будет ломаться - это хороший код.

К сожалению, мы живём в неидеальном мире и здесь всегда что-нибудь ломается.

В нашем примере, если moneyService отвалится после выполнения withdraw и до выполнения deposit, кому-то не дойдут денежки.

И после первой жалобы клиента на такой случай и разбора полётов начинается долгая и упорная борьба программиста с реальным миром.

Проблему надо как-то решать

Первое, что приходит в голову - реализовать некую стейт-машину для хранения такущего состояния. Придумаем механизм сериализации этого состояния, будем хранить его в базе и обновлять его после каждого шага.

Это позволит, в случае отказа системы на промежуточном шаге, восстановить состояние и продолжить работу.

Но теперь обычным REST запросом transfer вызывать уже не представляется возможным, навернём какую-нибудь очередь и будем их, как для вызова всех функций и общения между сервисами.

Добавим механизм повтора запросов к moneyService в случае ошибок, также на очередях.

Чтобы повторы происходили инкрементально, нам нужны таймеры и какой-нибудь планировщик задач, чтобы периодически пробегаться по таблице текущих задач и исполнял их. (И таблица тоже нужна)

Всё? Наверно, но теперь база становится узким местом, нужно добавить кэш. И думать про его актуализацию и инвалидацию.

И конечно не стоит забывать про безопасную многопоточность, чтобы не получилось, например, такой ситуации, когда от количества обращений в базу состояние после withdraw ещё не записалось, а сообщение на выполнение deposit уже в очереди и начинает обрабатываться другим потоком.

Теперь точно всё?…

Наверно, но вместе с вами те же самые проблемы решают ваши коллеги из других команд.

В итоге помимо того, что речи о простых 2 строчках уже давно не идёт, так и архитектура приложения теперь выглядит как-то так:

Может по-другому договоримся?

Как всегда, умные люди за нас уже всё подумали и решили сделать отдельную систему на которую можно спихнуть эти рутинные задачи: Все таймеры, повторы, обработчики ошибок, очереди и логи они вынесли.

А нам дали удобные SDK на множестве языков для работы со всем этим добром.

Так что мы можем писать код, как будто всё вокруг работает как надо и ошибок не существует. Сам код слегка видоизменится, так как мы не напрямую вызываем эти функции, а через Temporal:

public function transfer(
    Account $from,
    Account $to,
    int $amount
): \Generator {
    yield $this->moneyService->withdraw($from, $amount);
    yield $this->moneyService->deposit($to, $amount);
}

Вот полноценный пример на GitHub

Так что же такое Temporal?

Даже после года работы с ним я всё ещё не могу объяснить это, чтобы все поняли. Но не перестану пробовать.

В двух словах: Temporal даёт инструменты для написания надёжных функций, которые точно выполнятся.

Всё ещё не понятно? Не беда, в следующих статьях буду пробовать объяснять на примерах.