> 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/referencia/sdk-csharp/skailcommand.md).

# \[SkailCommand]

Marca um método como a unidade de I/O do fluxo: executa, tem argumentos e resultado registrados no histórico da execução, e não executa de novo naquela execução depois de concluir. É onde mora tudo que a function não pode fazer: I/O, relógio, aleatoriedade.

## Assinatura

{% tabs %}
{% tab title="C#" %}

```csharp
[SkailCommand]
[SkailCommand(retryCount: 10)]
public async SkailTask NomeDoMetodo(...)      // ou SkailTask<T>
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { SkailCommand } from "@skail/runtime";

export class Assinaturas {
  @SkailCommand()
  async nomeDoMetodo(): Promise<void> {}

  @SkailCommand({ retryCount: 10 })
  async comRetry(): Promise<string> {
    return "ok";
  }
}
```

{% endtab %}
{% endtabs %}

| Parâmetro    | Tipo   | Default | Descrição                                                                                                |
| ------------ | ------ | ------- | -------------------------------------------------------------------------------------------------------- |
| `retryCount` | `uint` | `5`     | Máximo de reentregas quando o command lança exceção. Ao esgotar, a exceção é lançada dentro da function. |

## Regras do método

| Regra                                                            | Detalhe                                                                                                                                                                                                                                                               | Quem cobra                                                            |
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Método de instância, `async`, retorno `SkailTask`/`SkailTask<T>` | Como em `[SkailFunction]`                                                                                                                                                                                                                                             | Analyzer (SKAIL001, SKAIL002)                                         |
| `public`                                                         | Sempre `public`                                                                                                                                                                                                                                                       | Analyzer (SKAIL001)                                                   |
| Argumentos e retorno serializáveis                               | `System.Text.Json`; records e DTOs pequenos, ids em vez de entidades                                                                                                                                                                                                  | Runtime, ao registrar o passo                                         |
| Chamado de dentro de uma function                                | Fora de uma execução durável não há contexto                                                                                                                                                                                                                          | Runtime                                                               |
| Não chama outro `[SkailCommand]` nem uma `[SkailFunction]`       | Quem compõe os passos é sempre a function, e commands são chamados só de dentro de functions. Se dois passos precisam ser duráveis separadamente, a function chama os dois em sequência; para reaproveitar lógica dentro do command, use métodos comuns, sem atributo | Analyzer (SKAIL006) para outro command; regra do modelo para function |

Dentro do corpo vale tudo que vale em C# comum:

* `HttpClient`;
* `DbContext`;
* `Task.Delay`;
* `Task.WhenAll`;
* `Guid.NewGuid()`;
* `DateTime.UtcNow`.

O command não tem a restrição de determinismo da function. As restrições são não chamar outro command nem uma function (ver a tabela acima) e não bloquear uma `Task` com `.GetAwaiter().GetResult()` (SKAIL005; use `await`).

## Comportamento

Quando a function alcança `await MeuCommand(...)`, o runtime confere o histórico da execução. Se este passo já foi concluído (a execução está sendo retomada), devolve o resultado daquela vez sem executar. Se não foi, executa o corpo uma vez; ao concluir, registra argumentos e retorno como um passo novo.

Se o corpo lança exceção, o runtime registra a falha e devolve a execução ao skail. Na reentrega, a function é reexecutada (passos anteriores voltam do histórico) e o command roda de novo. Isso se repete até concluir ou até esgotar `retryCount`; então a exceção é lançada no `await` da function, como qualquer exceção de `async/await`. O runtime não classifica exceções: toda exceção gera nova tentativa enquanto houver `retryCount`. Ver [Falhas, retries e idempotência](/construa/conceitos/retries-e-idempotencia.md).

{% hint style="warning" %}
A operação externa é "no mínimo uma vez": se o command falhar entre executar a operação externa e gravar o resultado, roda de novo. Use uma chave de idempotência derivada dos argumentos em toda chamada externa. Ver [Como escrever commands idempotentes](/construa/como-fazer/escrever-commands-idempotentes.md).
{% endhint %}

## Quando usar

Quando o método faz algo que não pode ser perdido nem repetido sem controle (gravar, movimentar, enviar, reservar), ou quando ele produz um valor que muda entre execuções (hora atual, id gerado, resposta de API). Um bom critério: se o resultado pode diferir entre duas execuções com as mesmas entradas, é command. Use um command por operação de I/O e devolva a falha de negócio como retorno, sem lançar exceção. Se o seu código sinaliza a falha por exceção, use `retryCount: 0` e trate na function, como em [Como tratar erros e compensar](/construa/como-fazer/tratar-erros-e-desfazer-passos.md).

## retryCount

Maior (8, 10) para integrações com instabilidade conhecida, onde repetir é seguro e barato. Zero só desliga a retentativa por exceção: a exceção chega à function na primeira falha e você decide. Em C#, para a exceção manter o tipo original depois de uma retomada, com qualquer `retryCount`, dê à classe um construtor com um único parâmetro `string`, que recebe a mensagem; ver [Como tratar erros e compensar](/construa/como-fazer/tratar-erros-e-desfazer-passos.md). Se o processo cair entre executar a operação externa e gravar o resultado, o command roda outra vez na retomada, então a chave de idempotência (ou uma tabela de controle no seu banco) continua necessária mesmo com zero. Escolha o `retryCount` de acordo com o caso, sem depender do valor padrão.

## Exemplo

{% tabs %}
{% tab title="C#" %}

```csharp
using System.Net.Http.Json;
using Skail.Platform.Runtime;
using Skail.Platform.Runtime.Threading;

public enum ResultadoCobranca { Aprovado, Recusado }

public record RespostaGateway(bool Approved);

public class Cobranca
{
    private readonly HttpClient _gateway = new() { BaseAddress = new Uri("https://gateway.exemplo.com") };

    [SkailCommand(retryCount: 8)]
    public async SkailTask<ResultadoCobranca> CobrarCartao(Guid faturaId, decimal valor)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/charges")
        {
            Content = JsonContent.Create(new { amount = valor, invoice = faturaId })
        };
        request.Headers.Add("Idempotency-Key", $"cobranca-{faturaId}");

        var resposta = await _gateway.SendAsync(request);
        resposta.EnsureSuccessStatusCode();                       // falha técnica: exceção, retentada

        var corpo = await resposta.Content.ReadFromJsonAsync<RespostaGateway>();
        return corpo!.Approved ? ResultadoCobranca.Aprovado : ResultadoCobranca.Recusado;   // falha de negócio: resultado
    }

    [SkailCommand]
    public async SkailTask<DateTime> ObterDataAtual() => await Task.FromResult(DateTime.UtcNow);
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { SkailCommand } from "@skail/runtime";

type ResultadoCobranca = "Aprovado" | "Recusado";

interface RespostaGateway {
  approved: boolean;
}

export class Cobranca {
  private readonly gatewayUrl = "https://gateway.exemplo.com";

  @SkailCommand({ retryCount: 8 })
  async cobrarCartao(faturaId: string, valor: number): Promise<ResultadoCobranca> {
    const resposta = await fetch(`${this.gatewayUrl}/charges`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Idempotency-Key": `cobranca-${faturaId}`,
      },
      body: JSON.stringify({ amount: valor, invoice: faturaId }),
    });
    if (!resposta.ok) throw new Error(`Gateway respondeu ${resposta.status}`); // falha técnica: exceção, retentada

    const corpo = (await resposta.json()) as RespostaGateway;
    return corpo.approved ? "Aprovado" : "Recusado"; // falha de negócio: resultado
  }

  @SkailCommand()
  async obterDataAtual(): Promise<string> {
    return new Date().toISOString();
  }
}
```

{% endtab %}
{% endtabs %}

## Observabilidade

Cada command aparece na linha do tempo da execução no Monitor com início, fim, duração, número de tentativas e erro, e abre um span de rastreamento filho da function.

## Erros relacionados

| Erro                                                                                                   | Causa                                                                            |
| ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| SKAIL001 / SKAIL002                                                                                    | Assinatura fora das regras                                                       |
| SKAIL006                                                                                               | Command que chama outro command                                                  |
| SKAIL005                                                                                               | `.GetResult()` em `Task` dentro do command                                       |
| Exceção de serialização                                                                                | Argumento ou retorno não serializável (stream, entidade com ciclo)               |
| Exceção do command chega à function                                                                    | `retryCount` esgotado; trate na function ou ajuste                               |
| (C#) Exceção chega à function como `SkailException`, com "cannot be persisted to durable state" no log | Classe de exceção sem construtor `(string message)`; dê esse construtor à classe |
| Operação externa duplicada                                                                             | Falha entre a operação externa e a gravação sem chave de idempotência            |

## Veja também

[SkailFunction](/referencia/sdk-csharp/skailfunction.md), [Como tratar erros e compensar](/construa/como-fazer/tratar-erros-e-desfazer-passos.md), [Como passar dados entre função e commands](/construa/como-fazer/passar-dados-entre-funcao-e-commands.md).
