Sarıkaya Dev Logo

Orchestrating Distributed Workflows in Laravel with Temporal.io and Serverless Workers

Mahmut Sarıkaya 5 min read 3 Views 0
Orchestrating Distributed Workflows in Laravel with Temporal.io and Serverless Workers

Why Laravel Needs Distributed Workflow Orchestration

Ever tried to coordinate a payment, inventory lock, and email notification in a single Laravel request and hit time‑outs or race conditions? Modern e‑commerce platforms, SaaS onboarding flows, and IoT pipelines often involve dozens of micro‑services that must succeed or roll back as a unit. Traditional Laravel queues handle fire‑and‑forget tasks well, but they lack built‑in state persistence, guaranteed exactly‑once execution, and cross‑service compensation.

Temporal.io fills that gap by turning a chain of asynchronous calls into a durable, observable workflow. When combined with Laravel’s expressive syntax, developers can write business logic in plain PHP while Temporal guarantees durability across crashes, network partitions, and scaling events. The result is a predictable, audit‑ready process without sacrificing Laravel’s developer ergonomics.

Temporal.io Basics and PHP SDK Overview

Temporal separates two concepts: the workflow – a deterministic state machine written in your language, and the activity – the actual code that talks to external services. The PHP SDK (temporal/sdk-php) mirrors the Go and Java clients, exposing a fluent API that integrates with Laravel’s service container.

Installation is a single Composer command, and the SDK ships with a CLI that launches workers, registers namespaces, and visualizes history. Because Temporal stores every event in its own PostgreSQL or MySQL cluster, a workflow can survive a full application redeploy without losing progress.

composer require temporal/sdk-php

After the package is installed, add the service provider in config/app.php:

use Temporal\Laravel\TemporalServiceProvider;\n\n// Inside the providers array\nTemporalServiceProvider::class,

Setting Up a Serverless Worker with AWS Lambda

Running a Temporal worker on a traditional VM is straightforward, but serverless reduces cost for intermittent workloads. AWS Lambda supports custom runtimes, and the PHP SDK can be bundled into a zip file that the Lambda runtime extracts at invocation.

First, create a minimal bootstrap.php that boots Laravel, resolves the worker, and starts it. The worker should listen to a single task queue – for example laravel-order-queue – to keep the cold‑start footprint low.

<?php\nrequire __DIR__ . '/vendor/autoload.php';\n$app = require __DIR__ . '/bootstrap/app.php';\n$app->make(Illuminate\Support\Facades\App::class);
\nuse Temporal\Worker\WorkflowWorker;\nuse App\Workflows\OrderWorkflow;\n\n$worker = WorkflowWorker::create('laravel-order-queue');\n$worker->registerWorkflowTypes(OrderWorkflow::class);\n$worker->run();

Deploy the zip with the AWS CLI:

aws lambda create-function \
    --function-name laravel-temporal-worker \
    --runtime provided.al2 \
    --handler bootstrap.php \
    --zip-file fileb://function.zip \
    --role arn:aws:iam::123456789012:role/lambda-exec-role \
    --environment Variables={TEMPORAL_NAMESPACE=default,TEMPORAL_TASK_QUEUE=laravel-order-queue}

Configure the Lambda trigger to poll Temporal’s gRPC endpoint (exposed via a public Load Balancer) or use an EventBridge rule that fires every minute to keep the worker alive.

Building a Sample Order Processing Workflow

Consider an e‑commerce scenario where an order must (1) reserve stock, (2) charge the customer, (3) send a confirmation email, and (4) schedule a shipping job. Each step is an activity that may fail and require compensation.

Define the workflow interface and implementation:

namespace App\Workflows;\n\nuse Temporal\Workflow\WorkflowInterface;\nuse Temporal\Workflow\WorkflowMethod;\n\n#[WorkflowInterface]\ninterface OrderWorkflowInterface\n{\n    #[WorkflowMethod(name: 'orderProcessing')]\n    public function process(string $orderId): void;\n}\n\nclass OrderWorkflow implements OrderWorkflowInterface\n{\n    public function process(string $orderId): void\n    {\n        $this->executeActivity(ReserveStockActivity::class, [$orderId]);\n        $this->executeActivity(ChargePaymentActivity::class, [$orderId]);\n        $this->executeActivity(SendConfirmationActivity::class, [$orderId]);\n        $this->executeActivity(ScheduleShippingActivity::class, [$orderId]);\n    }\n\n    private function executeActivity(string $activityClass, array $args)\n    {\n        $options = (new \Temporal\Activity\ActivityOptions())
            ->withStartToCloseTimeout(new \DateInterval('PT30S'))
            ->withRetryOptions((new \Temporal\Activity\RetryOptions())
                ->withMaximumAttempts(3));\n        \Temporal\Activity\ActivityStub::newStub($activityClass, $options)->execute(...$args);\n    }\n}

Implement one activity, for example stock reservation, using Laravel’s Eloquent model:

namespace App\Activities;\n\nuse Temporal\Activity\ActivityInterface;\nuse Temporal\Activity\ActivityMethod;\nuse App\Models\Product;\n\n#[ActivityInterface]\nclass ReserveStockActivity\n{\n    #[ActivityMethod]\n    public function execute(string $orderId): void\n    {\n        $order = \App\Models\Order::findOrFail($orderId);\n        foreach ($order->items as $item) {\n            $product = Product::findOrFail($item->product_id);\n            if ($product->stock < $item->quantity) {\n                throw new \Exception('Insufficient stock for product '.$product->id);\n            }\n            $product->decrement('stock', $item->quantity);\n        }\n    }\n}

Trigger the workflow from a Laravel controller:

use Temporal\Client\WorkflowClient;\nuse App\Workflows\OrderWorkflowInterface;\n\npublic function placeOrder(Request $request)\n{\n    $order = Order::create($request->all());\n    $client = WorkflowClient::create();\n    $client->newWorkflowStub(OrderWorkflowInterface::class)
        ->process($order->id);\n    return response()->json(['status' => 'accepted', 'order_id' => $order->id]);\n}

Monitoring, Retries and Scaling

Temporal’s Web UI shows real‑time execution graphs, history events, and failure stack traces. By default, each activity gets three retries with exponential back‑off; you can fine‑tune this per activity as shown in the workflow code. Serverless workers scale automatically when you increase the Lambda provisioned concurrency, while the Temporal server continues to queue tasks even if all workers are idle.

For alerting, integrate Temporal’s metrics endpoint with CloudWatch or Prometheus. A typical alert monitors “pending workflow tasks > 100” which indicates that workers are not keeping up. Adjust the Lambda memory size or enable “reserved concurrency” to guarantee a baseline capacity during peak order spikes.

Best Practices and Common Pitfalls

1. Keep workflows deterministic – avoid calling random generators, time‑based functions, or external APIs directly. Use activities for any side effect.
2. Version workflows carefully. Temporal supports “continue as new” to migrate long‑running processes without losing state.
3. Limit activity payload size. Pass identifiers (e.g., order ID) instead of full models; re‑hydrate inside the activity to keep gRPC messages lightweight.
4. Test locally with the Temporal Docker Compose stack before deploying to production. The stack includes a UI, a database, and a matching service that mirrors the cloud environment.

Finally, remember that serverless workers introduce cold‑start latency. Warm the Lambda function by scheduling a dummy heartbeat every 5 minutes, or consider using AWS Fargate for workloads with strict latency requirements.

Sources

Temporal Documentation – https://docs.temporal.io
Laravel Official Documentation – https://laravel.com/docs
AWS Lambda Developer Guide – https://docs.aws.amazon.com/lambda

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #Laravel #Temporal.io #distributed workflows #PHP SDK #serverless
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

4 + 6 =