Slide 1

Slide 1 text

Fault-tolerant work fl ow orchestration in PHP Sebastian Grodzicki @ PHPCon Poland 2024

Slide 2

Slide 2 text

Sebastian Grodzicki • CTO @ • ex-Google/Elastic/SHOWROOM/GoldenLine • PHP developer for 20+ years • PHPCon speaker/attendee for 10+ years •  @sebgrodzicki phpinfo();

Slide 3

Slide 3 text

The Need for Fault Tolerance • Application failures and crashes • Network outages • Flaky endpoints • Long-running processes • …and more.

Slide 4

Slide 4 text

Event-Driven Architectures

Slide 5

Slide 5 text

SAGA & Distributed Transactions

Slide 6

Slide 6 text

State Machines

Slide 7

Slide 7 text

Batch Processing

Slide 8

Slide 8 text

Scheduled Jobs & Cron

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Why Temporal? • Reliable distributed applications • Productive development paradigms and code structure • Visible distributed application state

Slide 11

Slide 11 text

Why Temporal? Temporal works with your preexisting choices for: • runtime, • test framework, • deployment, • continuous integration, • web frameworks, • …and more.

Slide 12

Slide 12 text

Why Temporal? Temporal allows you to develop with durable execution in di ff erent languages and multiple languages can be used to build single services, enabling polyglot development.

Slide 13

Slide 13 text

Why Temporal? Temporal is built in the open and released under the MIT license. It has been endorsed by some of the world's best companies and is loved by a growing, vibrant community.

Slide 14

Slide 14 text

– Rob Zienert, Senior Software Engineer @ Net fl ix "Net fl ix engineers spend less time writing logic to maintain application consistency or guard against failures because Temporal does it for them. The Temporal platform is easy to operate and fi ts naturally in our development work fl ow."

Slide 15

Slide 15 text

– Mitchell Hashimoto, Co-founder @ Hashicorp "Temporal's technology satis fi ed all of these requirements out of the box and allowed our developers to focus on business logic. Without Temporal's technology, we would've spent a signi fi cant amount of time rebuilding Temporal and would've very likely done a worse job."

Slide 16

Slide 16 text

– Sebastian Grodzicki, CTO @ Levity "Temporal has been a game-changer for our team. By abstracting away the complexities of building scalable distributed systems, it has allowed us to focus on delivering value to our customers at a much faster pace."

Slide 17

Slide 17 text

The Building Blocks • Work fl ow: your business logic, de fi ned in code, outlining each step in your process. • Activities: individual units of work in your Work fl ow. • SDK: everything needed to build Work fl ows, Activities, and various other Temporal features in a speci fi c programming language. • Temporal Service: a set of services and components on your own infrastructure (or Temporal Cloud).

Slide 18

Slide 18 text

Building Work fl ows in PHP • PHP SDK: $ composer require temporal/sdk • Temporal CLI: $ brew install temporal (on macOS) $ temporal server start-dev

Slide 19

Slide 19 text

Demo: Money Transfer #[WorkflowInterface] interface AccountTransferWorkflowInterface { #[WorkflowMethod(name: "MoneyTransfer")] public function transfer( string $fromAccountId, string $toAccountId, string $referenceId, int $amountCents ); }

Slide 20

Slide 20 text

Demo: Money Transfer #[ActivityInterface(prefix: "MoneyTransfer.")] interface AccountInterface { public function deposit(string $accountId, string $referenceId, int $amountCents): void; public function withdraw(string $accountId, string $referenceId, int $amountCents): void; }

Slide 21

Slide 21 text

Demo: Money Transfer class AccountTransferWorkflow implements AccountTransferWorkflowInterface { private AccountInterface $account; public function __construct() { $this->account = Workflow::newActivityStub( AccountInterface::class, ActivityOptions::new() ->withStartToCloseTimeout(CarbonInterval::seconds(5)) ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(10)) ); } public function transfer(string $fromAccountId, string $toAccountId, string $referenceId, int $amountCents) { yield $this->account->withdraw($fromAccountId, $referenceId, $amountCents); yield $this->account->deposit($toAccountId, $referenceId, $amountCents); } }

Slide 22

Slide 22 text

Demo: Money Transfer class AccountActivity implements AccountInterface { public function deposit(string $accountId, string $referenceId, int $amountCents): void { $this->log( "Withdraw to %s of %d cents requested. ReferenceId=%s\n", $accountId, $amountCents, $referenceId ); // throw new \RuntimeException("simulated"); // Uncomment to simulate failure } public function withdraw(string $accountId, string $referenceId, int $amountCents): void { $this->log( "Deposit to %s of %d cents requested. ReferenceId=%s\n", $accountId, $amountCents, $referenceId ); } }

Slide 23

Slide 23 text

Demo: Money Transfer class ExecuteCommand extends Command { protected const NAME = 'money-transfer'; protected const DESCRIPTION = 'Execute MoneyTransferWorkflow'; public function execute(InputInterface $input, OutputInterface $output): int { $workflow = $this->workflowClient->newWorkflowStub(AccountTransferWorkflowInterface::class); $output->writeln("Starting MoneyTransferWorkflow... "); // runs in blocking mode $workflow->transfer( 'fromID', 'toID', 'refID', 1000 ); $output->writeln("Workflow complete"); return self::SUCCESS; } }

Slide 24

Slide 24 text

Demo: Subscription #[WorkflowInterface] interface SubscriptionWorkflowInterface { #[WorkflowMethod] public function subscribe( string $userID ); }

Slide 25

Slide 25 text

Demo: Subscription #[ActivityInterface(prefix: "Subscription.")] interface AccountActivityInterface { public function sendWelcomeEmail(string $userID): void; public function chargeMonthlyFee(string $userID): void; public function sendEndOfTrialEmail(string $userID): void; public function sendMonthlyChargeEmail(string $userID): void; public function sendSorryToSeeYouGoEmail(string $userID): void; public function processSubscriptionCancellation(string $userID): void; }

Slide 26

Slide 26 text

Demo: Subscription class SubscriptionWorkflow implements SubscriptionWorkflowInterface { private $account; public function __construct() { $this->account = Workflow::newActivityStub( AccountActivityInterface::class, ActivityOptions::new() ->withScheduleToCloseTimeout(CarbonInterval::seconds(2)) ); } public function subscribe(string $userID){...} }

Slide 27

Slide 27 text

Demo: Subscription class SubscriptionWorkflow implements SubscriptionWorkflowInterface { public function subscribe(string $userID) { yield $this->account->sendWelcomeEmail($userID); try { $trialPeriod = true; while (true) { // Lower period duration to observe workflow behaviour yield Workflow::timer(CarbonInterval::days(30)); if ($trialPeriod) { yield $this->account->sendEndOfTrialEmail($userID); $trialPeriod = false; continue; } yield $this->account->chargeMonthlyFee($userID); yield $this->account->sendMonthlyChargeEmail($userID); } } catch (CanceledFailure $e) { yield Workflow::asyncDetached( function () use ($userID) { yield $this->account->processSubscriptionCancellation($userID); yield $this->account->sendSorryToSeeYouGoEmail($userID); } ); } } }

Slide 28

Slide 28 text

Demo: Subscription class SubscribeCommand extends Command { protected const NAME = 'subscribe:start'; protected const DESCRIPTION = 'Execute Subscription\SubscriptionWorkflow with custom user ID'; protected const ARGUMENTS = [ ['userID', InputArgument::REQUIRED, 'Unique user ID'] ]; public function execute(InputInterface $input, OutputInterface $output): int{...} }

Slide 29

Slide 29 text

Demo: Subscription class SubscribeCommand extends Command { public function execute(InputInterface $input, OutputInterface $output): int { $userID = $input->getArgument('userID'); $workflow = $this->workflowClient->newWorkflowStub( SubscriptionWorkflowInterface::class, WorkflowOptions::new() ->withWorkflowId('subscription:' . $userID) ->withWorkflowIdReusePolicy(IdReusePolicy::POLICY_ALLOW_DUPLICATE) ); $output->writeln("Start SubscriptionWorkflow... "); try { $run = $this->workflowClient->start($workflow, $userID); } catch (WorkflowExecutionAlreadyStartedException $e) { $output->writeln('Already running'); return self::SUCCESS; } $output->writeln( sprintf( 'Started: WorkflowID=%s, RunID=%s', $run->getExecution()->getID(), $run->getExecution()->getRunID(), ) ); return self::SUCCESS; } }

Slide 30

Slide 30 text

Demo: Subscription class CancelCommand extends Command { protected const NAME = 'subscribe:cancel'; protected const DESCRIPTION = 'Cancel Subscription\SubscriptionWorkflow for user ID'; protected const ARGUMENTS = [ ['userID', InputArgument::REQUIRED, 'Unique user ID'] ]; public function execute(InputInterface $input, OutputInterface $output): int { $userID = $input->getArgument('userID'); $workflow = $this->workflowClient->newUntypedRunningWorkflowStub('subscription:' . $userID); try { $workflow->cancel(); $output->writeln('Cancelled'); } catch (WorkflowNotFoundException $e) { $output->writeln('Already stopped'); } return self::SUCCESS; } }

Slide 31

Slide 31 text

Event-Driven Architectures

Slide 32

Slide 32 text

SAGA & Distributed Transactions

Slide 33

Slide 33 text

State Machines

Slide 34

Slide 34 text

Batch Processing

Slide 35

Slide 35 text

Scheduled Jobs & Cron

Slide 36

Slide 36 text

Resources • https://temporal.io • https://docs.temporal.io/develop/php/ • https://learn.temporal.io/ • https://github.com/temporalio/samples-php

Slide 37

Slide 37 text

Questions?

Slide 38

Slide 38 text

Thank you!

Slide 39

Slide 39 text

joind.in/talk/56805