> 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/escrever-fluxos/como-migrar-codigo-existente.md).

# Como migrar código existente

Como pegar um fluxo que já existe no seu sistema (jobs, filas, tabela de controle) e transformá-lo em uma function skail, sem reescrever o sistema. Em quatro passos, com o código antes e depois.

## Pré-requisitos

O runtime configurado no projeto (ou em um Worker Service novo ao lado do sistema): [Como configurar o runtime](/construir/configurar-e-hospedar/como-configurar-o-runtime.md). Acesso ao código do fluxo que vai migrar e ao banco que ele usa.

## 1. Escolha um fluxo, não o sistema

O candidato certo tem várias etapas que gravam, cobram ou enviam algo, espera por algo externo, e dá trabalho quando quebra: reservar estoque, esperar o pagamento, gerar etiqueta, esperar a transportadora, atualizar status. Hoje isso está espalhado em um job que roda a cada cinco minutos, uma tabela `pedido_status`, um consumidor de fila e um e-mail do atendimento quando trava. Deixe o resto do sistema como está.

## 2. Desenhe a function como o fluxo linear que ele sempre foi

Escreva a sequência de etapas como um método, do jeito que você descreveria para um colega. Cada etapa que grava, chama ou envia algo é uma chamada; cada espera é um `WaitForEvent`; cada prazo, um `Delay`.

```csharp
using Skail.Platform.Runtime.Standard;
using Skail.Platform.Runtime.Standard.Threading;

public static class Eventos
{
    public const string PagamentoConfirmado = "PAGAMENTO_CONFIRMADO";
    public const string PedidoEntregue      = "PEDIDO_ENTREGUE";
}

public sealed class FulfillmentService
{
    private readonly ErpDbContext _db;
    public FulfillmentService(ErpDbContext db) => _db = db;

    [SkailFunction]
    public async SkailTask ProcessarPedido(Guid pedidoId)
    {
        await ReservarEstoqueNoErp(pedidoId);

        var pagamento = SkailTask.WaitForEvent<PagamentoConfirmado>(Eventos.PagamentoConfirmado, pedidoId.ToString());
        var prazoPagamento = SkailTask.Delay(TimeSpan.FromDays(2));
        if (await SkailTask.WhenAny(pagamento, prazoPagamento) == prazoPagamento)
        {
            await LiberarEstoqueNoErp(pedidoId);
            return;
        }

        var etiqueta = await GerarEtiquetaDeEnvio(pedidoId, (await pagamento).FormaPagamento);

        var entrega = await SkailTask.WaitForEvent<ConfirmacaoEntrega>(Eventos.PedidoEntregue, etiqueta.CodigoRastreio);
        await AtualizarStatusNoErp(pedidoId, entrega);
    }
}
```

O job de cinco minutos, a tabela de status como máquina de estados e o consumidor de fila desaparecem: a function é a máquina de estados, e o histórico da execução é a tabela.

## 3. Extraia as operações de I/O para commands

Cada trecho que toca banco, HTTP ou gera valores vira um `[SkailCommand]` público, `async`, retornando `SkailTask`. O código dentro dele é o que você já tinha; a diferença é a fronteira.

```csharp
    [SkailCommand]
    public async SkailTask ReservarEstoqueNoErp(Guid pedidoId)
    {
        var itens = await _db.PedidoItens.Where(i => i.PedidoId == pedidoId).ToListAsync();
        foreach (var item in itens)
        {
            var estoque = await _db.Estoques.SingleAsync(e => e.ProdutoId == item.ProdutoId);
            estoque.QuantidadeDisponivel -= item.Quantidade;
            estoque.QuantidadeReservada  += item.Quantidade;
        }
        var pedido = await _db.Pedidos.SingleAsync(p => p.Id == pedidoId);
        pedido.Status = "estoque_reservado";
        await _db.SaveChangesAsync();
    }

    [SkailCommand]
    public async SkailTask<EtiquetaGerada> GerarEtiquetaDeEnvio(Guid pedidoId, string formaPagamento)
    {
        var codigoRastreio = $"BR{DateTime.UtcNow:yyyyMMddHHmmss}";   // relógio e Guid: permitidos aqui, dentro do command
        _db.EtiquetasEnvio.Add(new EtiquetaEnvioEntity
        {
            Id = Guid.NewGuid(), PedidoId = pedidoId, CodigoRastreio = codigoRastreio,
            FormaPagamento = formaPagamento, CriadaEm = DateTime.UtcNow
        });
        var pedido = await _db.Pedidos.SingleAsync(p => p.Id == pedidoId);
        pedido.Status = "etiqueta_gerada";
        await _db.SaveChangesAsync();
        return new EtiquetaGerada(codigoRastreio);
    }

    [SkailCommand]
    public async SkailTask AtualizarStatusNoErp(Guid pedidoId, ConfirmacaoEntrega entrega)
    {
        var pedido = await _db.Pedidos.SingleAsync(p => p.Id == pedidoId);
        pedido.Status = "entregue";
        pedido.EntregueEm = entrega.DataHora;
        await _db.SaveChangesAsync();
    }

    [SkailCommand]
    public async SkailTask LiberarEstoqueNoErp(Guid pedidoId) { /* inverso de ReservarEstoqueNoErp */ }
```

Os três testes ao extrair: o método faz I/O ou lê relógio? Vira command. O método só decide sobre valores já conhecidos? Fica na function. O método recebe ou devolve uma entidade de ORM? Troque por id e record ([Como passar dados](/construir/escrever-fluxos/como-passar-dados-entre-funcao-e-commands.md)). E lembre que `ReservarEstoqueNoErp` precisa ser idempotente: se rodar duas vezes por uma falha na gravação, o estoque não pode ser reservado em dobro ([Como escrever commands idempotentes](/construir/escrever-fluxos/como-escrever-commands-idempotentes.md)).

## 4. Conecte as pontas

Quem iniciava o fluxo (a tela que fecha o pedido) passa a fazer `POST /trigger/erp/v1.0.0/ProcessarPedido/{pedidoId}` com `["{pedidoId}"]`. Quem sinalizava o pagamento (o webhook do gateway) passa a fazer `POST /api/v1/fire/PAGAMENTO_CONFIRMADO/{pedidoId}` com o payload. A transportadora idem, com `PEDIDO_ENTREGUE` e o código de rastreio. Ver [Como disparar uma função](/construir/escrever-fluxos/como-disparar-uma-funcao-pela-api-http.md) e [Como disparar um evento](/construir/escrever-fluxos/como-disparar-um-evento-pela-api-http.md).

O que sai do sistema: o job agendado, a tabela de controle do fluxo (a tabela de negócio `pedidos` fica), o consumidor de fila desse fluxo e o código de retry. O que fica: tudo o mais.

## O que muda de `Task` para `SkailTask`

Nas assinaturas dos métodos decorados, `Task` vira `SkailTask` e `Task<T>` vira `SkailTask<T>`; o resto do código é igual. Dentro dos commands, `Task.Delay`, `Task.WhenAll` e `HttpClient` continuam iguais. Dentro da function, `Task.Delay` vira `SkailTask.Delay`, `Task.WhenAll` vira `SkailTask.WhenAll`, e qualquer I/O ou relógio vai para um command. Métodos auxiliares `async Task` que a function chamava e que fazem I/O também viram commands; se são lógica pura, viram métodos síncronos comuns.

## Avance aos poucos

Um fluxo por vez, começando pelo que mais dói. Cada fluxo migrado é independente dos outros; o sistema continua inteiro enquanto isso. O exemplo completo, com o cenário de negócio e a operação no Monitor, está em [Modernização de um legado](/aprender/exemplos-completos/como-modernizar-seu-sistema-legado-sem-reescrever-tudo-com-skail.md).

## Próximos passos

[Padrões recomendados](/construir/padroes-e-antipadroes/padroes-recomendados.md) antes do primeiro pull request. [Como testar funções](/construir/testar-depurar-e-observar/como-testar-funcoes-com-skail.platform.runtime.testing.md) para cobrir o fluxo migrado. [Como hospedar](/construir/configurar-e-hospedar/como-hospedar-worker-service-asp.net-core-container-kubernetes-azure.md) para decidir se o runtime roda no processo do sistema ou em um worker ao lado.
