Communityライティング&編集github.com

gplanchat/durable-skill

Claude Codeスキル:PHPでDurableワークフロー、アクティビティ、Nexusオペレーションを作成するためのスキル

durable-skill とは?

このスキルは、Claude Codeを使用してPHPでDurableワークフロー、アクティビティ、およびNexusオペレーションを記述するためのものです。TemporalのようなDurable実行エンジン上で動作するワークフローは、プレーンなPHPクラスとして定義され、属性(アトリビュート)を使ってワークフロー名やメソッドを指定します。アクティビティはインターフェースとして契約を定義し、実際の副作用は別の実装クラスで行います。Nexusは異なるチームやデプロイメント間でのサービス間通信を可能にします。このスキルの核心は、ワークフロー本体が再実行されるという決定論のルールにあります。乱数や現在時刻の直接使用、データベースクエリ、HTTPコールなど非決定論的な操作は禁止され、代わりにアクティビティや環境メソッドを使用します。アクティビティはリトライ可能であり、冪等性に注意して実装する必要があります。また、すべてのスケジュール可能な操作はAwaitableを返し、awaitによって明示的に待機することで並列実行も制御できます。このスキルはSymfony、Sylius、MagentoといったPHPフレームワークに対応しています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add gplanchat/durable-skill

Installed? Explore more ライティング&編集 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Durableコードの記述

ワークフローは プレーンなPHPクラス です。ベースクラス、インターフェース、フレームワークタイプは必要ありません。シグネチャにそれらは含まれません。ワークフローをDurableにするのは、エンジンがジャーナルからワークフローをリプレイするという点です。したがって、唯一の制約は決定論です。

3つの形状

#[AsWorkflow('checkout')]                       // クラスターが認識する名前
final class CheckoutWorkflow
{
    public function __construct(
        private readonly WorkflowEnvironment $environment,
    ) {}

    #[AsWorkflowMethod]                          // ワークフロークラスにつき1つだけ
    public function run(string $orderId): string
    {
        $orders = $this->environment->activityStub(OrderActivities::class);

        $receipt = $this->environment->await($orders->charge($orderId));
        $this->environment->sleep(Duration::seconds(30), 'クールダウン中');

        return $this->environment->await($orders->notify($receipt));
    }
}
interface OrderActivities                        // 契約が名前を保持する
{
    #[AsActivityMethod(name: 'shop.order.charge')]
    public function charge(string $orderId): string;
}

final class DoctrineOrderActivities implements OrderActivities   // ハンドラーが処理を実行する
{
    public function charge(string $orderId): string { /* 実際の副作用はここに */ }
}
#[AsNexusService('billing')]                     // 別のチーム、別のデプロイメント
interface BillingContract
{
    #[AsNexusOperation('check')]
    public function check(string $order, int $amountInCents, string $currency): array;
}

呼び出し元でアクティビティ名を文字列として書かないでください。 スタブは契約の属性を読み取るため、名前は一箇所にのみ存在します。既存のワークフローを壊す最も一般的な方法は、メソッド名を変更し属性をそのままにしておくことです。そうすると、実行中のワークフローはアクティビティを見つけられなくなります。

制約のルール: ワークフロー本体はリプレイされる

#[AsWorkflowMethod] 内のすべては、エンジンが実行を再開するたびに(おそらく別のプロセスで、数日後)、最初から再実行されます。そのため、リプレイ時にも同じ結果を返す必要があります。

ワークフロー本体で禁止代わりに使用するもの
rand(), uniqid(), time(), new \DateTime()$environment->sideEffect(fn() => …) — 結果は一度だけジャーナルに記録される
sleep(), usleep()$environment->sleep($duration, '待機理由')
データベースクエリ、HTTP呼び出し、ファイル書き込みアクティビティ
if (getenv(…)) でデプロイ時に分岐$environment->version($changeId, $min, $max)

アクティビティは非決定論が属する場所です。アクティビティは試行ごとに1回実行され、その結果はジャーナルに書き込まれ、リプレイ時にはコードの代わりにジャーナルが応答します。

⚠ アクティビティはリトライされます。2回目の試行が安全になるように記述してください。 エンジンは結果が一度だけ記録されることを保証しますが、副作用が一度だけ実行されることを保証するわけではありません。カードを二重に請求することは、フレームワークのバグではなくビジネスインシデントです。

待機

スケジュール可能なすべてのものは Awaitable を返します。awaitするまでは何も起こりません — これが並列性を実現する方法でもあります。

$a = $stub->reserve($order);                     // スケジュールされたが、待機はされていない
$b = $stub->quote($order);
[$reserved, $quoted] = $this->environment->await($this->environment->all($a, $b));

$first = $this->environment->await($this->environment->any($a, $b));
$twoOfThree = $this->environment->await($this->environment->some(2, $a, $b, $c));

await() はオプションの期限を受け付けます:await($awaitable, Duration::minutes(5))。

リファレンスファイル

すべてではなく、必要なものだけを読んでください。

  • references/workflows.md — シグナル、更新、子ワークフロー、タイマー、continueAsNew、バージョニング、そして実行の開始方法(Webリクエスト内でインラインにはしない)。
  • references/activities.md — ActivityOptions、リトライポリシー、4つのタイムアウト、再試行不可の例外、ハートビート、キャンセル。
  • references/nexus.md — Nexus契約、ワークフローがオペレーションを完了するときに契約が2つに分割される理由、そして静かに失敗するペイロードの落とし穴。
  • references/migrations.md — 既存のコードに触れる前にこれを読んでください。 Temporal PHP SDKからの移行、またはDurableのあるバージョンから次のバージョンへの移行:gplanchat/durable-rector が書き換えを行い、リファレンスは主にそのルールが推測を拒否する5つのことについて説明しています。そのうち2つは、すでに実行中のサーバー上で静かに失敗します。

コードを返却する前に

  • ワークフロークラスごとに1つの #[AsWorkflowMethod] があり、そのクラスはホストに宣言されています(Symfony/Syliusは自動設定、Magentoは di.xml にリスト)。
  • すべてのアクティビティ呼び出しは契約から構築されたスタブを経由します。文字列名は使用しません。
  • ワークフロー本体に非決定論的なものはありません。
  • すべてのタイマーにサマリーがあります。サマリーは監視タイムラインの行に名前を付け、TimerScheduled は午前3時にオペレーターに何も伝えません。
  • ワークフローは補償できないものをキャッチしません。未処理の失敗は実行を終了し、可視化されます。飲み込まれた失敗は注文が中途半端に処理されたまま静かに放置されます。

関連スキル