Mengintegrasikan Temporal ke RoadRunner untuk durable execution: memahami konsep workflow dan activity, menulis workflow PHP dengan SDK Temporal, mengonfigurasi plugin temporal, dan membangun contoh sistem order-payment yang tahan kegagalan.

Setelah di episode 18 kita mengelola dependensi, pada episode kali ini kita masuk ke puncak ekosistem: Temporal workflow plugin. Queue biasa (episode 11) memproses satu job yang berjalan beberapa menit. Tapi bagaimana dengan proses bisnis yang berlangsung berhari-hari — order menunggu pembayaran, kemudian verifikasi, lalu pengiriman — dan harus selamat dari server crash di tengah jalan? Itulah masalah yang dipecahkan Temporal.
Mengapa episode ini penting? Karena durable execution adalah pembeda RoadRunner dari app server lain: bukan sekadar menjalankan PHP lebih cepat, tetapi mengeksekusi proses bisnis yang tidak akan pernah kehilangan state meski mesin mati. Ini yang membuat RoadRunner cocok untuk sistem transaksional nyata.
Temporal adalah platform durable execution. Tiga konsep intinya:
| Konsep | Arti |
|---|---|
| Workflow | Orkestrasi langkah-langkah — kode yang mencatat setiap keputusan |
| Activity | Eksekusi aktual (memanggil API, mengirim email, menulis DB) |
| Durable execution | State workflow tersimpan di Temporal server; crash tidak menghapus progres |
| Worker | Proses yang menjalankan workflow/activity — di sini, worker RoadRunner |
Keajaibannya: jika server mati di tengah Activity, Temporal server mengingat posisinya dan melanjutkan saat worker hidup lagi — tanpa kehilangan state.
temporal:
address: 127.0.0.1:7233
activities:
num_workers: 4
max_worker_memory: 128
codec: json
cache_size: 512| Key | Fungsi |
|---|---|
address | Alamat Temporal server |
activities | Pool worker untuk menjalankan workflow/activity |
codec | Encoding payload (json/proto) |
cache_size | Cache workflow context per worker |
Worker PHP Temporal memakai package spiral/roadrunner-temporal:
composer require spiral/roadrunner-temporal temporal/sdkActivity adalah pekerjaan aktual. Contoh untuk sistem order-payment:
<?php
declare(strict_types=1);
use Temporal\Activity\ActivityInterface;
use Temporal\Activity\ActivityMethod;
#[ActivityInterface(prefix: 'order')]
final class OrderActivities
{
#[ActivityMethod(name: 'CreateOrder')]
public function createOrder(array $data): string
{
// Simpan order ke DB, kembalikan order id
return $this->db->insert('orders', $data);
}
#[ActivityMethod(name: 'VerifyPayment')]
public function verifyPayment(string $orderId): bool
{
return $this->paymentProvider->check($orderId);
}
#[ActivityMethod(name: 'SendConfirmation')]
public function sendConfirmation(string $orderId, string $email): void
{
$this->mailer->send($email, 'Konfirmasi Order ' . $orderId);
}
}Setiap activity sebaiknya idempoten (bisa diulang tanpa efek ganda) — Temporal dapat mengulang activity saat worker restart.
Workflow mengorkestrasi activity. Workflow PHP adalah kelas dengan atribut:
<?php
declare(strict_types=1);
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;
use Temporal\Workflow\Workflow;
#[WorkflowInterface]
final class OrderWorkflow
{
#[WorkflowMethod(name: 'OrderFlow')]
public function run(array $order): array
{
$orderId = yield Workflow::executeActivity(
OrderActivities::class . '::CreateOrder',
$order,
);
// Tunggu pembayaran — bisa berjam-jam, tahan terhadap restart
$paid = yield Workflow::executeActivity(
OrderActivities::class . '::VerifyPayment',
$orderId,
);
if (!$paid) {
yield Workflow::executeActivity(
OrderActivities::class . '::SendConfirmation',
$orderId,
$order['email'],
);
return ['status' => 'pending', 'order_id' => $orderId];
}
yield Workflow::executeActivity(
OrderActivities::class . '::SendConfirmation',
$orderId,
$order['email'],
);
return ['status' => 'confirmed', 'order_id' => $orderId];
}
}Aturan emas workflow: jangan pernah memanggil I/O langsung di dalam workflow — semua efek samping harus lewat activity. Workflow harus deterministic, karena setiap eksekusi ulang (replay) harus menghasilkan keputusan yang sama. Hanya activity yang boleh berinteraksi dengan dunia luar.
Warning
Hindari time(), rand(), koneksi DB, atau HTTP langsung di dalam workflow. RoadRunner + Temporal me-replay logika workflow untuk memulihkan state; kode nondeterministik akan membuat replay menghasilkan hasil berbeda — sumber bug yang sangat sulit dilacak.
Daftarkan workflow/activity di worker:
use Spiral\RoadRunner\Temporal\TemporalWorker;
use Temporal\WorkerFactory;
$worker = WorkerFactory::create();
$worker->registerWorkflowTypes(OrderWorkflow::class);
$worker->registerActivityImplementations(new OrderActivities($db));
$factory = TemporalWorker::create($worker);
$factory->run();Memulai workflow dari aplikasi (misal setelah checkout):
use Temporal\Client\WorkflowClient;
$client = WorkflowClient::create();
$handle = $client->newWorkflowStub(OrderWorkflow::class);
$promise = $client->start($handle, ['email' => 'user@example.com', 'total' => 150000]);| Kebutuhan | Queue (ep. 11) | Temporal |
|---|---|---|
| Pekerjaan sekali jalan dalam detik-menit | Ya | Berlebihan |
| Proses multi-langkah dengan state antar langkah | Sulit | Ideal |
| Menunggu kondisi eksternal berjam-jam/hari | Manual | Bawaan |
| Retry dan sagas | Manual | Bawaan |
| Timeline/audit eksekusi | Tidak | Ya |
Pada episode 19 ini, kalian telah membuka kekuatan durable execution.
Inti yang harus dibawa pulang:
temporal mengelola worker; SDK PHP menulis workflow/activity.Di episode 20 selanjutnya, kita belajar worker pool tuning & performance — menyetel num_workers, max_jobs, max_worker_memory, supervisor, opcache dan JIT, lalu membandingkan RoadRunner dengan PHP-FPM dan FrankenPHP. Sampai jumpa di episode 20!