Skip to content

Exemplos de Workflows — arqel-dev/workflow

Esta pasta contém três exemplos completos e comentados de máquinas de estado construídas com o pacote arqel-dev/workflow. Cada um foi escolhido para exercitar um conjunto distinto de features do pacote, juntos eles cobrem ~todas as decisões de design que aparecem em apps Laravel reais.

Use estes exemplos como ponto de partida quando for desenhar seu próprio workflow: copie a estrutura, adapte os estados/transições ao seu domínio, e remova o que não usar.

Os três exemplos

ExemploDomínioFoco principal
order-states.mdE-commerce — pedidosAutorização por papel (authorizeFor + Gate), webhooks de transportadora, transição "any-to" (Cancelled), idempotência via metadata
article-states.mdCMS editorial — artigosFluxo colaborativo humano, rejeição com feedback, autorização 100% via Gate, integração com arqel-dev/versioning para snapshots de conteúdo
subscription-states.mdSaaS billing — assinaturasTransições disparadas por webhooks Stripe, side-effects em cache/quotas, sistema como ator (humanos têm acesso limitado), uso intensivo de metadata para auditoria

Tabela comparativa de features

Feature do arqel-dev/workfloworderarticlesubscription
WorkflowDefinition + HasWorkflow traitsimsimsim
StateTransitionField no Resourcesimsimsim
StateFilter na Tablesimsimsim
Authorization via authorizeFor()simsim
Authorization via Gate (transition-X-to-Y)simsim
Listener de StateTransitioned (queued)simsimsim
Transição "any-to-X" (múltiplos from())sim (Cancelled)sim (Canceled)
Side-effects em side-systems (email/cache/queue)emailemail + jobscache + quotas + email + métricas
Uso de metadata no históricotracking_code, webhook_event_idfeedbackwebhook_event_id, event_type, recovery
Idempotência via metadata->webhook_event_idsimsim (central)
Sistema como ator (user null)parcial (webhook delivery)sim (Stripe webhooks)
Estado terminal sem retornosim (Archived)sim (Canceled)
Integração com arqel-dev/versioningsim
defaultFilters na Tablesimsim
Validação de domínio na Gate (filled(...))sim (refund_reason)

Padrões transversais que valem ser destacados

Slugify de transitions. O TransitionAuthorizer deriva o nome da Gate como transition-{from-slug}-to-{to-slug}, onde slug é a última parte do FQCN do state, sem o sufixo State, em kebab-case. OrderState\Pendingpending, ArticleState\InReviewin-review, SubscriptionState\PastDuepast-due.

Listener único vs múltiplos. O exemplo de article usa um listener com match($event->to) para os três casos relevantes — é simples e legível para handlers leves. subscription faz o mesmo (um listener para todas as transições) porque os side-effects compartilham serviços (FeatureFlagCache, QuotaManager). Já order separa em listeners distintos (NotifyCustomerOfShipment, etc.) porque cada um tem dependências e níveis de criticidade muito diferentes. Não existe regra dura — escolha o que ficar mais legível para seu time.

Estado vs Action. Quando você se pega tentado a adicionar uma transição Archived → Draft ou Canceled → Active, pare. A regra é: máquina de estados representa o ciclo de vida natural do recurso. Operações que "ressuscitam" um recurso terminal devem ser Actions que criam um novo registro (duplicar artigo, criar nova subscription) — preservando o histórico do antigo.

Metadata como contrato. Em todos os três exemplos o metadata da transição segue um schema implícito: campos como webhook_event_id, subscription_id, feedback, tracking_code aparecem repetidamente. Documente esse schema no SKILL.md do seu app — auditores e desenvolvedores futuros vão te agradecer.

Como usar estes exemplos

  1. Leia o exemplo cujo domínio mais se assemelha ao seu.
  2. Copie o model + workflow definition + transition classes para o seu app, adaptando nomes.
  3. Decida sua estratégia de autorização (Gate, authorizeFor, ou mix) lendo a tabela acima.
  4. Escreva os listeners para os side-effects relevantes — comece com um único listener match() se forem simples.
  5. Adicione o StateTransitionField no Resource e o StateFilter na Table.
  6. Escreva testes Pest 3 para cada transição (ver packages/workflow/tests/Feature no monorepo para padrões).

Veja também

Licença MIT — construído com Inertia + React + Laravel.