CommunityEscrita e Ediçãogithub.com

gplanchat/skill-duravel

Skill para Claude Code: escrever workflows duráveis, activities e operações Nexus em PHP.

O que é skill-duravel?

Este skill capacita o Claude Code a gerar código PHP para workflows duráveis usando o ecossistema Temporal. Ele abstrai a complexidade da orquestração de processos de longa duração, garantindo que o desenvolvedor foque na lógica de negócio sem se preocupar com replay, timeouts ou paralelismo. O skill suporta três formas principais: **Workflows** (classes PHP puras anotadas com `#[AsWorkflow]` e `#[AsWorkflowMethod]`), **Activities** (interfaces que definem contratos com `#[AsActivityMethod]`) e **Nexus Operations** (para comunicação entre diferentes deployments via contratos `#[AsNexusService]`). Ele reforça a regra fundamental do determinismo: tudo que é não-determinístico (como chamadas HTTP, consultas a banco, geração de números aleatórios) deve ser delegado a uma activity, e o workflow jamais deve conter efeitos colaterais não registrados. O skill também documenta padrões de espera com `await()`, `all()`, `any()` e `some()`, além de fornecer referências para migrations (`gplanchat/durable-rector`), sinais, updates, child workflows, timers com sumário descritivo e política de retry com heartbeats. É compatível com Symfony, Sylius (autoconfigure) e Magento (via `di.xml`).

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add gplanchat/durable-skill

Installed? Explore more Escrita e Edição skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Escrevendo código durável

Um workflow é uma classe PHP normal. Sem classe base, sem interface, sem tipo de framework em sua assinatura. O que o torna durável é que o motor o reproduz a partir de um diário, então a única regra que realmente importa é o determinismo.

As três formas

#[AsWorkflow('checkout')]                       // o nome que o cluster conhece
final class CheckoutWorkflow
{
    public function __construct(
        private readonly WorkflowEnvironment $environment,
    ) {}

    #[AsWorkflowMethod]                          // exatamente um por classe de workflow
    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), 'resfriando');

        return $this->environment->await($orders->notify($receipt));
    }
}
interface OrderActivities                        // o CONTRATO carrega os nomes
{
    #[AsActivityMethod(name: 'shop.order.charge')]
    public function charge(string $orderId): string;
}

final class DoctrineOrderActivities implements OrderActivities   // o MANIPULADOR faz o trabalho
{
    public function charge(string $orderId): string { /* efeitos colaterais reais aqui */ }
}
#[AsNexusService('billing')]                     // outra equipe, outro deploy
interface BillingContract
{
    #[AsNexusOperation('check')]
    public function check(string $order, int $amountInCents, string $currency): array;
}

Nunca escreva um nome de activity como string no local de chamada. O stub lê os atributos do contrato, portanto o nome vive em exatamente um lugar. Esta é a forma mais comum de quebrar um workflow existente: renomear o método, manter o atributo e as execuções em andamento ainda encontram sua activity.

A regra que vincula: o corpo do workflow é reproduzido

Tudo dentro de um método #[AsWorkflowMethod] é executado novamente, do início, toda vez que o motor retoma a execução — possivelmente em outro processo, dias depois. Portanto, o corpo deve dar as mesmas respostas quando reproduzido:

Nunca no corpo do workflowUse em vez disso
rand(), uniqid(), time(), new \DateTime()$environment->sideEffect(fn() => …) — o resultado é registrado uma vez no diário
sleep(), usleep()$environment->sleep($duration, 'por que esperamos')
Uma consulta ao banco, uma chamada HTTP, uma escrita em arquivouma activity
if (getenv(…)) para bifurcar com base no deploy$environment->version($changeId, $min, $max)

Uma activity é onde o não-determinismo pertence. Ela é executada uma vez por tentativa, seu resultado é escrito no diário e, na reprodução, o diário responde em vez do código.

⚠ Uma activity é tentada novamente; escreva-a de forma que uma segunda tentativa seja segura. O motor garante que o resultado seja registrado uma vez, não que seu efeito colateral o seja. Cobrar um cartão duas vezes é um incidente de negócio, não um bug do framework.

Esperando

Tudo que pode ser agendado retorna um Awaitable. Nada acontece até que você o aguarde — que também é como você obtém paralelismo:

$a = $stub->reserve($order);                     // agendado, não aguardado
$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() aceita um prazo opcional: await($awaitable, Duration::minutes(5)).

Arquivos de referência

Leia o que você precisa, não todos os três:

  • references/workflows.md — sinais, atualizações, child workflows, timers, continueAsNew, versionamento e como uma execução é iniciada (nunca inline em uma requisição web).
  • references/activities.md — ActivityOptions, política de retry, os quatro timeouts, exceções não-repetíveis, heartbeats, cancelamento.
  • references/nexus.md — Contratos Nexus, por que um contrato se divide em dois quando um workflow cumpre uma operação, e a armadilha de payload que falha silenciosamente.
  • references/migrations.md — leia isto antes de tocar em código existente. Saindo do Temporal PHP SDK, ou movendo de uma versão do Durable para a próxima: gplanchat/durable-rector faz a reescrita, e a referência é principalmente sobre as cinco coisas que suas regras se recusam a adivinhar — duas das quais falham silenciosamente, em um servidor que já está em execução.

Antes de devolver o código

  • Um #[AsWorkflowMethod] por classe de workflow, e a classe é declarada ao host (Symfony/Sylius autoconfiguram; Magento lista em di.xml).
  • Toda chamada de activity passa por um stub construído a partir de um contrato — sem nomes em string.
  • Nada não-determinístico no corpo do workflow.
  • Todo timer tem um resumo: é o que nomeia a linha na linha do tempo de observação, e TimerScheduled não diz nada a um operador às 3h da manhã.
  • O workflow não captura o que não pode compensar. Uma falha não tratada encerra a execução e é visível; uma engolida deixa um pedido meio processado e silencioso.

Habilidades Relacionadas