> 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/construa/casos-de-uso/modernizar-um-sistema-legado.md).

# Modernizar um sistema legado

Se você tem um sistema legado em produção, provavelmente já viveu isso:

* fluxos quebrando no meio
* retries manuais
* status inconsistentes
* dificuldade de entender o que aconteceu, etc.

Modernizar um sistema não significa refazer tudo.

Na maioria dos casos, o caminho mais rápido é identificar onde estão os gargalos (geralmente fluxos longos, com esperas, retries manuais e integrações frágeis) e evoluir só essa parte.

O restante continua no legado, fazendo bem o que já faz:

* cadastros
* relatórios
* regras fiscais
* integrações já consolidadas

Com o skail, você separa responsabilidades de forma simples:

* o legado continua executando operações
* o skail passa a coordenar o fluxo (orquestrando)

E isso pode começar pequeno, com um único fluxo, e evoluir aos poucos.

#### Como o skail entra no seu sistema legado

Imagine um ERP legado que cria pedidos, controla estoque e emite nota.\
Tudo isso funciona razoavelmente bem.

O problema está no fluxo:

* reservar estoque
* esperar pagamento
* gerar etiqueta
* aguardar confirmação da transportadora
* atualizar status

Cada etapa depende da anterior, e é aí que começam os problemas.

Isso normalmente fica espalhado entre:

* jobs agendados
* filas
* tabelas de controle
* emails de atendimento pedindo para retomar o processo manualmente quando algo trava

Esse mesmo fluxo, no skail, vira uma simples função linear:

<figure><img src="https://1898946878-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJmS5ILPG5x2x5j0WyiV%2Fuploads%2FDH0HXbX2WERvE2ijrjIw%2Fap-legado-fluxo.svg?alt=media" alt="Mapa da function ProcessarPedido, de cima para baixo: reserva o estoque no ERP, espera o evento PAGAMENTO_CONFIRMADO, gera a etiqueta de envio, espera o evento PEDIDO_ENTREGUE e atualiza o status no ERP. Fora da function, o ERP inicia o fluxo pelo trigger quando cria o pedido, e cada espera é acordada por um POST no fire: o pagamento pelo id do pedido, a entrega pelo código de rastreio."><figcaption></figcaption></figure>

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

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

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 = await SkailTask.WaitForEvent<PagamentoConfirmado>(
            "PAGAMENTO_CONFIRMADO",
            pedidoId.ToString()
        );

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

        var entrega = await SkailTask.WaitForEvent<ConfirmacaoEntrega>(
            "PEDIDO_ENTREGUE",
            etiqueta.CodigoRastreio
        );

        await AtualizarStatusNoERP(pedidoId, entrega);
    }

    [SkailCommand]
    public async SkailTask ReservarEstoqueNoERP(Guid pedidoId)
    {
        var pedido = await _db.Pedidos.SingleAsync(p => p.Id == pedidoId);

        // Idempotente: se uma tentativa anterior já reservou, não reserva de novo.
        if (pedido.Status == "estoque_reservado")
        {
            return;
        }

        var itens = await _db.PedidoItens
            .Where(item => item.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;
        }

        pedido.Status = "estoque_reservado";

        await _db.SaveChangesAsync();
    }

    [SkailCommand]
    public async SkailTask<EtiquetaGerada> GerarEtiquetaDeEnvio(
        Guid pedidoId,
        string formaPagamento
    )
    {
        // Idempotente: se uma tentativa anterior já gerou a etiqueta, devolve a mesma.
        var existente = await _db.EtiquetasEnvio
            .SingleOrDefaultAsync(e => e.PedidoId == pedidoId);

        if (existente is not null)
        {
            return new EtiquetaGerada
            {
                CodigoRastreio = existente.CodigoRastreio
            };
        }

        var codigoRastreio = $"BR{DateTime.UtcNow:yyyyMMddHHmmss}";

        var etiqueta = new EtiquetaEnvioEntity
        {
            Id = Guid.NewGuid(),
            PedidoId = pedidoId,
            CodigoRastreio = codigoRastreio,
            FormaPagamento = formaPagamento,
            CriadaEm = DateTime.UtcNow
        };

        _db.EtiquetasEnvio.Add(etiqueta);

        var pedido = await _db.Pedidos.SingleAsync(p => p.Id == pedidoId);
        pedido.Status = "etiqueta_gerada";

        await _db.SaveChangesAsync();

        return new EtiquetaGerada
        {
            CodigoRastreio = 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;
        pedido.Recebedor = entrega.Recebedor;

        await _db.SaveChangesAsync();
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// fulfillment.ts
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";
import {
  ErpDb,
  type EstoqueEntity,
  type EtiquetaEnvioEntity,
  type PedidoEntity,
  type PedidoItemEntity,
} from "./erp-db.js";
import type { ConfirmacaoEntrega, EtiquetaGerada, PagamentoConfirmado } from "./tipos.js";

export class FulfillmentService {
  private readonly db = new ErpDb();

  @SkailFunction()
  async ProcessarPedido(pedidoId: string): Promise<void> {
    await this.ReservarEstoqueNoERP(pedidoId);

    const pagamento = await Skail.waitForEvent<PagamentoConfirmado>("PAGAMENTO_CONFIRMADO", pedidoId);

    const etiqueta = await this.GerarEtiquetaDeEnvio(pedidoId, pagamento.formaPagamento);

    const entrega = await Skail.waitForEvent<ConfirmacaoEntrega>("PEDIDO_ENTREGUE", etiqueta.codigoRastreio);

    await this.AtualizarStatusNoERP(pedidoId, entrega);
  }

  @SkailCommand()
  async ReservarEstoqueNoERP(pedidoId: string): Promise<void> {
    await this.db.transaction(async (tx) => {
      const [pedido] = await tx.query<PedidoEntity>("SELECT * FROM pedidos WHERE id = $1", [pedidoId]);
      if (!pedido) throw new Error(`Pedido ${pedidoId} não encontrado`);

      // Idempotente: se uma tentativa anterior já reservou, não reserva de novo.
      if (pedido.status === "estoque_reservado") {
        return;
      }

      const itens = await tx.query<PedidoItemEntity>("SELECT * FROM pedido_itens WHERE pedido_id = $1", [pedidoId]);

      for (const item of itens) {
        const [estoque] = await tx.query<EstoqueEntity>("SELECT * FROM estoques WHERE produto_id = $1", [item.produto_id]);
        if (!estoque) throw new Error(`Estoque do produto ${item.produto_id} não encontrado`);

        await tx.query(
          "UPDATE estoques SET quantidade_disponivel = $1, quantidade_reservada = $2 WHERE produto_id = $3",
          [
            estoque.quantidade_disponivel - item.quantidade,
            estoque.quantidade_reservada + item.quantidade,
            item.produto_id,
          ],
        );
      }

      await tx.query("UPDATE pedidos SET status = 'estoque_reservado' WHERE id = $1", [pedidoId]);
    });
  }

  @SkailCommand()
  async GerarEtiquetaDeEnvio(pedidoId: string, formaPagamento: string): Promise<EtiquetaGerada> {
    return this.db.transaction(async (tx) => {
      // Idempotente: se uma tentativa anterior já gerou a etiqueta, devolve a mesma.
      const [existente] = await tx.query<EtiquetaEnvioEntity>("SELECT * FROM etiquetas_envio WHERE pedido_id = $1", [pedidoId]);

      if (existente) {
        return { codigoRastreio: existente.codigo_rastreio };
      }

      const agora = new Date();
      const codigoRastreio = `BR${agora.toISOString().replace(/\D/g, "").slice(0, 14)}`;

      await tx.query(
        `INSERT INTO etiquetas_envio (id, pedido_id, codigo_rastreio, forma_pagamento, criada_em)
         VALUES ($1, $2, $3, $4, $5)`,
        [crypto.randomUUID(), pedidoId, codigoRastreio, formaPagamento, agora.toISOString()],
      );

      await tx.query("UPDATE pedidos SET status = 'etiqueta_gerada' WHERE id = $1", [pedidoId]);

      return { codigoRastreio };
    });
  }

  @SkailCommand()
  async AtualizarStatusNoERP(pedidoId: string, entrega: ConfirmacaoEntrega): Promise<void> {
    await this.db.query(
      "UPDATE pedidos SET status = 'entregue', entregue_em = $1, recebedor = $2 WHERE id = $3",
      [entrega.dataHora, entrega.recebedor, pedidoId],
    );
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

O que antes estava espalhado, agora está explícito em um único lugar.

Dois pontos de espera, pagamento e entrega, orquestrados como um método linear.

Cada passo que mexe no ERP vira um command.\
Na prática, é a mesma operação que seu legado já conhece:

* consultar tabelas
* atualizar status
* salvar registros
* confirmar mudanças no banco

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
[SkailCommand]
public async SkailTask ReservarEstoqueNoERP(Guid pedidoId)
{
    var pedido = await _db.Pedidos.SingleAsync(p => p.Id == pedidoId);

    pedido.Status = "estoque_reservado";

    await _db.SaveChangesAsync();
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// reservar-simples.ts
import { SkailCommand } from "@skail/runtime";
import { ErpDb, type PedidoEntity } from "./erp-db.js";

export class FulfillmentService {
  private readonly db = new ErpDb();

  @SkailCommand()
  async ReservarEstoqueNoERP(pedidoId: string): Promise<void> {
    const [pedido] = await this.db.query<PedidoEntity>("SELECT * FROM pedidos WHERE id = $1", [pedidoId]);
    if (!pedido) throw new Error(`Pedido ${pedidoId} não encontrado`);

    await this.db.query("UPDATE pedidos SET status = 'estoque_reservado' WHERE id = $1", [pedidoId]);
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

O legado não precisa ser reescrito para isso.\
O skail apenas coordena quando cada etapa deve acontecer.

#### Estruturas usadas no exemplo

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
public sealed record PagamentoConfirmado
{
    public string TransacaoId { get; init; } = "";
    public string FormaPagamento { get; init; } = "";
}

public sealed record EtiquetaGerada
{
    public string CodigoRastreio { get; init; } = "";
}

public sealed record ConfirmacaoEntrega
{
    public DateTime DataHora { get; init; }
    public string Recebedor { get; init; } = "";
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// tipos.ts
export interface PagamentoConfirmado {
  transacaoId: string;
  formaPagamento: string;
}

export interface EtiquetaGerada {
  codigoRastreio: string;
}

export interface ConfirmacaoEntrega {
  dataHora: string;
  recebedor: string;
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
using Microsoft.EntityFrameworkCore;

public sealed class ErpDbContext : DbContext
{
    public ErpDbContext(DbContextOptions<ErpDbContext> options)
        : base(options)
    {
    }

    public DbSet<PedidoEntity> Pedidos => Set<PedidoEntity>();
    public DbSet<PedidoItemEntity> PedidoItens => Set<PedidoItemEntity>();
    public DbSet<EstoqueEntity> Estoques => Set<EstoqueEntity>();
    public DbSet<EtiquetaEnvioEntity> EtiquetasEnvio => Set<EtiquetaEnvioEntity>();
}

public sealed class PedidoEntity
{
    public Guid Id { get; set; }
    public string Status { get; set; } = "";
    public DateTime? EntregueEm { get; set; }
    public string? Recebedor { get; set; }
}

public sealed class PedidoItemEntity
{
    public Guid Id { get; set; }
    public Guid PedidoId { get; set; }
    public Guid ProdutoId { get; set; }
    public decimal Quantidade { get; set; }
}

public sealed class EstoqueEntity
{
    public Guid ProdutoId { get; set; }
    public decimal QuantidadeDisponivel { get; set; }
    public decimal QuantidadeReservada { get; set; }
}

public sealed class EtiquetaEnvioEntity
{
    public Guid Id { get; set; }
    public Guid PedidoId { get; set; }
    public string CodigoRastreio { get; set; } = "";
    public string FormaPagamento { get; set; } = "";
    public DateTime CriadaEm { get; set; }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// erp-db.ts
export interface PedidoEntity {
  id: string;
  status: string;
  entregue_em: string | null;
  recebedor: string | null;
}

export interface PedidoItemEntity {
  id: string;
  pedido_id: string;
  produto_id: string;
  quantidade: number;
}

export interface EstoqueEntity {
  produto_id: string;
  quantidade_disponivel: number;
  quantidade_reservada: number;
}

export interface EtiquetaEnvioEntity {
  id: string;
  pedido_id: string;
  codigo_rastreio: string;
  forma_pagamento: string;
  criada_em: string;
}

export interface Queryable {
  query<T>(sql: string, values?: readonly unknown[]): Promise<T[]>;
}

interface PgClient {
  query(sql: string, values?: readonly unknown[]): Promise<{ rows: unknown[] }>;
  release(): void;
}
interface PgPool {
  query(sql: string, values?: readonly unknown[]): Promise<{ rows: unknown[] }>;
  connect(): Promise<PgClient>;
}

export class ErpDb implements Queryable {
  static #pool: Promise<PgPool> | undefined;

  private static pool(): Promise<PgPool> {
    ErpDb.#pool ??= (async () => {
      const driver = "pg";
      const { Pool } = (await import(driver)) as { Pool: new (config: { connectionString?: string }) => PgPool };
      return new Pool({ connectionString: process.env.ERP_DATABASE });
    })();
    return ErpDb.#pool;
  }

  async query<T>(sql: string, values: readonly unknown[] = []): Promise<T[]> {
    return (await (await ErpDb.pool()).query(sql, values)).rows as T[];
  }

  async transaction<T>(work: (tx: Queryable) => Promise<T>): Promise<T> {
    const client = await (await ErpDb.pool()).connect();
    const tx: Queryable = { query: async <R>(sql: string, values: readonly unknown[] = []) => (await client.query(sql, values)).rows as R[] };
    try {
      await client.query("BEGIN");
      const result = await work(tx);
      await client.query("COMMIT");
      return result;
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Configuração do runtime

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
using Microsoft.EntityFrameworkCore;
using Skail.Platform.Runtime.Hosting;
using Skail.Platform.Runtime.Standard;

[assembly: VisibleToSkailPlatform]

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddDbContext<ErpDbContext>(options =>
{
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("ErpDatabase")
    );
});

builder.UseSkail();

await builder.Build().RunAsync();
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// main.ts
import { createSkailPlatform } from "@skail/runtime";

const controller = new AbortController();
for (const signal of ["SIGINT", "SIGTERM"] as const) {
  process.once(signal, () => controller.abort());
}

const platform = createSkailPlatform({
  workload: process.env.SKAIL_WORKLOAD ?? "erp:v1.0.0",
  bundle: ".skail/skail-functions.mjs",
  manifest: ".skail/manifest.json",
});

await platform.run(controller.signal);
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Como o legado inicia um fluxo no skail

Do ponto de vista do legado, nada muda.\
Ele só chama um endpoint HTTP normal.

Para iniciar uma function, use o endpoint de trigger:

```bash
curl -X POST \
  "https://SEU_SKAIL_BASE_URL/trigger/seu_workload/versao_workload/ProcessarPedido/00000000-0000-0000-0000-000000000000" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "skail-key: SUA_SKAIL_KEY" \
  -H "Skail-namespace: SEU_NAMESPACE" \
  -d '[
    "00000000-0000-0000-0000-000000000000"
  ]'
```

Quando o ERP conclui a criação do pedido, ele dispara esse trigger.

O skail recebe, inicia a execução durável e o ERP continua o próprio trabalho.

A execução pode durar dias; o ERP não precisa saber disso.

#### Como o skail reage ao que acontece no seu legado

Quando algo acontece no seu sistema, como pagamento aprovado, você só precisa avisar o skail.

Ele já dispara coisas quando algo acontece:

* uma chamada HTTP
* uma mensagem em fila
* ou até um registro no banco

A ideia aqui é simples: pegar esse sinal e transformar em algo que o skail entende.

Na prática, isso significa publicar um evento externo usando a API HTTP de fire.

Um webhook de pagamento vindo do gateway que o ERP já integra, por exemplo, vira:

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("webhooks/gateway-pagamento")]
public sealed class PagamentoWebhookController : ControllerBase
{
    private readonly HttpClient _skail; // cliente nomeado "skail": URL base e headers do skail

    public PagamentoWebhookController(IHttpClientFactory factory)
    {
        _skail = factory.CreateClient("skail");
    }

    [HttpPost]
    public async Task<IActionResult> Receber(
        [FromBody] NotificacaoGateway notificacao
    )
    {
        if (notificacao.Evento != "payment.approved")
        {
            return Ok();
        }

        var response = await _skail.PostAsJsonAsync(
            $"api/v1/fire/PAGAMENTO_CONFIRMADO/{notificacao.PedidoId}",
            new PagamentoConfirmado
            {
                TransacaoId = notificacao.TransactionId,
                FormaPagamento = notificacao.Method
            }
        );

        response.EnsureSuccessStatusCode();

        return Ok();
    }
}

public sealed record NotificacaoGateway
{
    public string Evento { get; init; } = "";
    public Guid PedidoId { get; init; }
    public string TransactionId { get; init; } = "";
    public string Method { get; init; } = "";
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// webhook.ts
import { skailPost } from "./skail-http.js";
import type { PagamentoConfirmado } from "./tipos.js";

export interface NotificacaoGateway {
  evento: string;
  pedidoId: string;
  transactionId: string;
  method: string;
}

export async function receber(notificacao: NotificacaoGateway): Promise<{ status: number }> {
  if (notificacao.evento !== "payment.approved") {
    return { status: 200 };
  }

  const payload: PagamentoConfirmado = {
    transacaoId: notificacao.transactionId,
    formaPagamento: notificacao.method,
  };

  const response = await skailPost(`api/v1/fire/PAGAMENTO_CONFIRMADO/${notificacao.pedidoId}`, payload);

  if (!response.ok) {
    throw new Error(`Falha ao disparar PAGAMENTO_CONFIRMADO: HTTP ${response.status}`);
  }

  return { status: 200 };
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

O cliente HTTP que chama o skail é configurado uma vez na aplicação que recebe o webhook, com a URL base e os headers `skail-key` e `Skail-namespace`:

{% tabs %}
{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
// Program.cs da aplicação que recebe o webhook (não é o worker do skail)
builder.Services.AddHttpClient("skail", client =>
{
    client.BaseAddress = new Uri(builder.Configuration["Skail:BaseUrl"]!.TrimEnd('/') + "/");
    client.DefaultRequestHeaders.Add("skail-key", builder.Configuration["Skail:Key"]);
    client.DefaultRequestHeaders.Add("Skail-namespace", builder.Configuration["Skail:Namespace"]);
});
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code lineNumbers="true" %}

```typescript
// skail-http.ts da aplicação que recebe o webhook (não é o worker do skail)
const baseUrl = process.env.SKAIL_BASE_URL!.replace(/\/+$/, "") + "/";

export function skailPost(path: string, body: unknown): Promise<Response> {
  return fetch(new URL(path, baseUrl), {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "skail-key": process.env.SKAIL_KEY!,
      "Skail-namespace": process.env.SKAIL_NAMESPACE!,
    },
    body: JSON.stringify(body),
  });
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Ou, diretamente por `curl`:

```bash
curl -X POST \
  "https://SEU_SKAIL_BASE_URL/api/v1/fire/PAGAMENTO_CONFIRMADO/00000000-0000-0000-0000-000000000000" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "skail-key: SUA_SKAIL_KEY" \
  -H "Skail-namespace: SEU_NAMESPACE" \
  -d '{
    "transacaoId": "txn_123",
    "formaPagamento": "cartao_credito"
  }'
```

Esse código só transforma o evento do formato do legado para o formato que o skail entende, e quem decide o que fazer é a function.

A função `ProcessarPedido`, que estava hibernada há minutos ou horas esperando o `PAGAMENTO_CONFIRMADO` daquele pedido, retoma automaticamente.

O mesmo padrão serve para confirmação de entrega da transportadora, aprovação de crédito que vem do banco, qualquer coisa que antes precisaria de um job consumindo uma fila e atualizando uma tabela de "próximo passo".

```bash
curl -X POST \
  "https://SEU_SKAIL_BASE_URL/api/v1/fire/PEDIDO_ENTREGUE/BR123456789" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "skail-key: SUA_SKAIL_KEY" \
  -H "Skail-namespace: SEU_NAMESPACE" \
  -d '{
    "dataHora": "2026-04-27T18:30:00Z",
    "recebedor": "Maria Silva"
  }'
```

{% hint style="info" %}
[Agende uma conversa grátis com um arquiteto de solução](https://skailhq.com/) para discutir seus objetivos
{% endhint %}

#### Avançando aos poucos

O primeiro fluxo modernizado geralmente convive com o fluxo antigo por um tempo: alguns pedidos vão pelo skail, outros pelo caminho de sempre, separados por uma flag.

À medida que a confiança aumenta, o percentual de tráfego no skail cresce, e no fim o fluxo antigo é desligado.

Depois, repita o processo com o segundo e o terceiro fluxo.

O legado vai perdendo responsabilidade em pontos específicos, mas não precisa ser reescrito; ele continua sendo o dono dos cadastros, das regras fiscais, dos relatórios.

O skail assume o que o legado nunca fez bem: controlar um processo que dura mais do que uma requisição HTTP.

#### Observabilidade

No Monitor, cada pedido em fulfillment aparece com seu estágio atual:

* aguardando pagamento
* aguardando entrega
* atualizando ERP

As operações no banco do ERP aparecem como commands, uma a uma, com latência e status.

Isso resolve um problema comum em sistemas legados: entender em que etapa um pedido está sem precisar correlacionar logs de vários sistemas diferentes.

No Monitor, você consegue ver exatamente:

* em que etapa o processo está
* onde ele parou, se falhou
* qual foi o motivo
* e retomar dali, se necessário

E como cada command fica registrado dentro da execução, você também consegue ver o que foi executado em cada etapa do legado.

Em vez de procurar em logs espalhados, você vê o fluxo completo no Monitor.

Além disso, mesmo que a execução seja retomada várias vezes ao longo dos dias, as etapas já concluídas não rodam de novo: o skail devolve o resultado gravado de cada command.

{% hint style="info" %}
Se um passo falhar depois de mexer no banco e antes de o resultado ser gravado, ele roda de novo. Por isso cada command do exemplo é idempotente:

* `ReservarEstoqueNoERP` confere o status do pedido antes de reservar;
* `GerarEtiquetaDeEnvio` devolve a etiqueta que já existe para o pedido;
* `AtualizarStatusNoERP` grava os mesmos valores se rodar duas vezes.

Veja [Como escrever commands idempotentes](/construa/como-fazer/escrever-commands-idempotentes.md).
{% endhint %}

***

Para modernizar o sistema, você não precisa trocar tudo: tire a complexidade dos fluxos longos e comece por um fluxo só.

{% hint style="info" %}
[Agende agora uma consultoria grátis](https://skailhq.com/) com um arquiteto especializado skail para falar sobre seu projeto
{% endhint %}
