> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construir/padroes-e-antipadroes/padroes-recomendados.md).

# Padrões recomendados

Onze regras que fazem um fluxo skail ser previsível em produção. Cada uma existe por causa de um mecanismo do produto (replay, memoização, retry, correlação de eventos), e a razão está escrita ao lado.

## 1. Function fina, command gordo

A `[SkailFunction]` só decide e encadeia. Toda operação de I/O (HTTP, banco, fila, e-mail) e toda fonte de não determinismo (relógio, aleatoriedade, variável de ambiente) moram em `[SkailCommand]`. Motivo: o corpo da function é reexecutado a cada retomada; o command roda uma vez e o resultado é gravado. Ver [Determinismo e replay](/aprender/fundamentos/determinismo-e-replay.md).

```csharp
[SkailFunction]
public async SkailTask EmitirNota(Guid pedidoId)
{
    var pedido = await CarregarPedido(pedidoId);     // command
    var xml    = MontarXml(pedido);                   // lógica pura, pode ficar na function
    var chave  = await EnviarParaPrefeitura(xml);     // command
    await GravarChave(pedidoId, chave);               // command
}
```

## 2. Um command por operação de I/O

Cobrar, emitir e notificar são três commands, não um. Motivo: quando um command falha, o retry reexecuta o command inteiro. Se ele fez três coisas, as duas que deram certo acontecem de novo. E um command não chama outro command nem uma function: quem compõe os passos é sempre a function, que chama um depois do outro quando eles precisam ser duráveis separadamente; lógica comum entre commands fica em métodos sem atributo. Ver [SkailCommand](/construir/sdk-.net/skailcommand.md).

## 3. Idempotência por chave de negócio dentro do command

Passe o id do pedido, da fatura ou da tentativa como chave de idempotência na chamada externa (header `Idempotency-Key`, campo do payload, ou `UPSERT` por chave no banco). Motivo: o skail garante que o command não roda de novo depois de gravar o resultado, mas se ele falhar entre o efeito e a gravação, roda de novo. Ver [Como escrever commands idempotentes](/construir/escrever-fluxos/como-escrever-commands-idempotentes.md).

## 4. Falha de negócio é resultado, não exceção

Cartão recusado, CPF inválido, estoque insuficiente: devolva um valor (`ResultadoCobranca.Recusado`) e decida na function. Motivo: qualquer exceção em command gera nova tentativa até o `retryCount`; uma recusa definitiva seria retentada cinco vezes à toa. Exceção é para o que pode dar certo na próxima tentativa.

## 5. instanceId de evento é um id de negócio estável

O segundo argumento do `WaitForEvent` e o último segmento do `/api/v1/fire` precisam ser idênticos, e o jeito de garantir isso é usar um id que os dois lados já conhecem: o id da fatura, da solicitação, da conversa. Nunca algo gerado na hora.

## 6. Nomes de evento são constantes compartilhadas

```csharp
public static class Eventos
{
    public const string PagamentoConfirmado = "PAGAMENTO_CONFIRMADO";
    public const string AprovacaoRecebida   = "APROVACAO_RECEBIDA";
}
```

Usadas na function (`WaitForEvent(Eventos.PagamentoConfirmado, ...)`) e no código que dispara (`$"/api/v1/fire/{Eventos.PagamentoConfirmado}/{id}"`). Motivo: um typo em uma string e o evento nunca correlaciona; a execução fica esperando para sempre.

## 7. Toda espera tem prazo

`WaitForEvent` sempre dentro de um `WhenAny` com `Delay`, e a function decide o que fazer no timeout. Motivo: um evento que nunca chega (webhook perdido, pessoa que não aprovou) não pode deixar a execução parada indefinidamente sem que ninguém saiba. Ver [Como esperar com timeout](/construir/escrever-fluxos/como-esperar-com-timeout.md).

```csharp
var confirmacao = SkailTask.WaitForEvent<ConfirmacaoPagamento>(Eventos.PagamentoConfirmado, faturaId.ToString());
var prazo       = SkailTask.Delay(TimeSpan.FromHours(48));

var vencedor = await SkailTask.WhenAny(confirmacao, prazo);
if (vencedor == prazo) { await CancelarPorExpiracao(faturaId); return; }
```

## 8. Argumentos e retornos pequenos, serializáveis e imutáveis

Ids e DTOs (`record`), não entidades de ORM, streams, `HttpResponseMessage` ou listas com milhares de itens. Para dados grandes, passe uma referência (id, URL, chave de blob) e carregue dentro do command que precisa. Motivo: argumentos e retornos são gravados no histórico da execução e desserializados a cada replay; histórico pesado é retomada lenta.

## 9. retryCount explícito quando o default não serve

`[SkailCommand(retryCount: 10)]` para integrações sabidamente instáveis; `retryCount: 0` para efeitos que não podem ser repetidos e que você prefere tratar na function na primeira falha. Não deixe o default por não ter pensado. Ver [Falhas, retries e idempotência](/aprender/fundamentos/falhas-retries-e-idempotencia.md).

## 10. Versione antes de mudar o fluxo de uma function com execuções em andamento

Novo método com `skailMethodName`, ou nova versão do workload; a versão antiga fica publicada até a última execução dela terminar. Motivo: mudar os `await`s de uma function com histórico gera `SkailNonDeterministicException` no replay. Ver [Versionamento](/aprender/fundamentos/versionamento-de-codigo-com-execucoes-em-andamento.md).

## 11. Nomes legíveis para quem olha o Monitor

`EmitirNotaFiscal`, `AguardarAprovacaoDoGerente`, não `Step3` ou `Process2`. Os nomes de function e command são o que aparece na linha do tempo do Monitor e nos spans de rastreamento. Inclua `SkailContext.Current.TaskId` em todo log.

## Checklist rápido

Antes de abrir o pull request de um fluxo skail:

* Nenhum I/O, relógio, `Guid.NewGuid()` ou `Random` dentro de uma function.
* Um command por operação de I/O; cada command com chave de idempotência na chamada externa.
* Falhas de negócio como retorno; `retryCount` pensado.
* Todo `WaitForEvent` com `WhenAny` e `Delay`; nomes de evento em constantes; instanceId de negócio.
* Argumentos e retornos são DTOs pequenos.
* Se a function já tem execuções em produção, o fluxo não mudou, ou foi versionado.

A versão completa está em [Checklist de revisão de código skail](/construir/padroes-e-antipadroes/checklist-de-revisao-de-codigo-skail.md).

## Próximos passos

[Antipadrões](/construir/padroes-e-antipadroes/antipadroes-o-que-nao-fazer-e-por-que.md) é o espelho desta página: o que não fazer, por que quebra e como corrigir. Os padrões de orquestração (Saga, Fan-out/Fan-in, Human-in-the-loop, Debounce/Aggregator, Timeout, Polling, Régua de lembretes) estão no mesmo grupo.
