> 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/quickstart/design-patterns/fan-in-fan-out.md).

# Fan-in / Fan-out

Algumas operações são paralelas por natureza. Você precisa fazer a mesma coisa para uma lista de entidades, coletar informação de várias fontes independentes, processar um lote grande dividindo-o em pedaços. Em vez de rodar essas N operações em sequência — esperando uma terminar para começar a próxima — você dispara todas ao mesmo tempo (fan-out) e junta o resultado quando todas terminarem (fan-in).

Em código comum, esse padrão logo encontra limites: threads ocupadas, timeout de requisições HTTP, fragilidade se alguma das operações falhar no meio. Em skail, as operações em paralelo são só múltiplas `[SkailFunction]` chamadas dentro de um `WhenAll`, cada uma com sua própria vida durável.

### Processamento em lote

Sua aplicação recebe um arquivo com 5.000 faturas para emitir, uma planilha com 20.000 cobranças, uma lista de 10.000 clientes para notificar. O loop sequencial trava qualquer servidor num lote razoável; o fan-out resolve de forma natural:

```csharp
[SkailFunction]
public async SkailTask ProcessarLoteDeFaturas(Guid loteId)
{
    var faturas = await CarregarFaturasDoLote(loteId);

    var execucoes = faturas
        .Select(f => EmitirFaturaIndividual(f.Id))
        .ToArray();

    await SkailTask.WhenAll(execucoes);

    await ConsolidarResultado(loteId);
}

[SkailFunction]
public async SkailTask EmitirFaturaIndividual(Guid faturaId)
{
    await GerarDocumentoFiscal(faturaId);
    await EnviarParaCliente(faturaId);
    await RegistrarEnvio(faturaId);
}
```

Cada fatura vira uma `[SkailFunction]` própria — com seu próprio estado, seu próprio ponto de hibernação, sua própria entrada no Monitor. O `WhenAll` coordena o fim do lote: `ConsolidarResultado` só roda depois que todas as execuções individuais tiverem terminado.

Entre o fan-out e o fan-in, o orquestrador está hibernado. Ele não ocupa worker. Enquanto as faturas individuais avançam, `ProcessarLoteDeFaturas` simplesmente espera o `WhenAll` resolver — podem ser minutos ou horas, dependendo do tempo total de processamento e das esperas que cada uma faz internamente.

### Coletando resultados

Em muitos cenários o orquestrador precisa não só saber que todas terminaram, mas também do resultado de cada uma — para produzir um relatório final, reagir a falhas parciais, juntar dados vindos de origens diferentes. Nesse caso, cada `[SkailFunction]` retorna o resultado e o fan-in os coleta:

```csharp
[SkailFunction]
public async SkailTask<RelatorioLote> ProcessarLoteDeFaturas(Guid loteId)
{
    var faturas = await CarregarFaturasDoLote(loteId);

    var execucoes = faturas
        .Select(f => EmitirFaturaIndividual(f.Id))
        .ToArray();

    await SkailTask.WhenAll(execucoes);

    var resultados = new List<ResultadoFatura>();
    foreach (var execucao in execucoes)
        resultados.Add(await execucao);

    var relatorio = new RelatorioLote
    {
        Total = resultados.Count,
        Sucesso = resultados.Count(r => r.Sucesso),
        Falhas = resultados.Where(r => !r.Sucesso).ToList()
    };

    await PersistirRelatorio(loteId, relatorio);
    return relatorio;
}

[SkailFunction]
public async SkailTask<ResultadoFatura> EmitirFaturaIndividual(Guid faturaId)
{
    try
    {
        await GerarDocumentoFiscal(faturaId);
        await EnviarParaCliente(faturaId);
        return new ResultadoFatura { FaturaId = faturaId, Sucesso = true };
    }
    catch (Exception ex)
    {
        return new ResultadoFatura { FaturaId = faturaId, Sucesso = false, Erro = ex.Message };
    }
}
```

Capturar a exceção dentro de cada execução individual e retornar um resultado estruturado impede que uma falha isolada derrube o lote inteiro. O `WhenAll` só propaga exceção se alguma execução lançar; com resultados tipados, o orquestrador consegue fechar o lote mesmo quando parte das faturas não saiu, registrando o motivo de cada falha para reprocessamento posterior.

### Fan-out em chunks

Fan-out ingênuo com N muito grande costuma ser indesejável: 10.000 execuções simultâneas podem saturar o sistema downstream (o gateway de envio, a API do ERP, o banco do estoque) mesmo que o runtime da skail dê conta. O padrão nesse caso é processar em ondas:

```csharp
[SkailFunction]
public async SkailTask ProcessarLoteGrande(Guid loteId)
{
    var faturas = await CarregarFaturasDoLote(loteId);

    foreach (var janela in faturas.Chunk(100))
    {
        var execucoes = janela
            .Select(f => EmitirFaturaIndividual(f.Id))
            .ToArray();

        await SkailTask.WhenAll(execucoes);
    }

    await ConsolidarResultado(loteId);
}
```

Cem execuções em paralelo por vez, até esgotar o lote. O resto do padrão continua o mesmo — cada execução individual é durável, o orquestrador hiberna entre uma onda e a próxima. Ajuste o tamanho da janela de acordo com a capacidade do serviço downstream.

### Consolidando fontes independentes

O mesmo padrão se aplica a cenários em que o paralelismo não é sobre um lote homogêneo, mas sobre coletar informação de fontes distintas. Um fechamento diário de uma rede com 50 lojas, por exemplo: cada loja tem seu próprio subsistema, seu próprio ritmo, e o consolidado só faz sentido quando todas responderam.

```csharp
[SkailFunction]
public async SkailTask<FechamentoRede> FechamentoDiario(DateOnly dia)
{
    var lojas = await ListarLojasAtivas();

    var fechamentos = lojas
        .Select(loja => FecharCaixaDaLoja(loja.Id, dia))
        .ToArray();

    await SkailTask.WhenAll(fechamentos);

    var resultados = new List<ResultadoFechamento>();
    foreach (var execucao in fechamentos)
        resultados.Add(await execucao);

    var consolidado = ConsolidarFechamentoRede(dia, resultados);
    await PersistirFechamentoRede(consolidado);
    return consolidado;
}
```

Do ponto de vista do código, a diferença para o lote de faturas é só semântica: fan-out sobre lojas em vez de faturas. A mecânica é idêntica.

### Observabilidade

No Monitor do portal do skail, cada execução individual do fan-out aparece como um item próprio. Você consegue filtrar pelo orquestrador (todas as 5.000 faturas do lote X, todas as 50 lojas do dia Y) e ver, lado a lado, quais terminaram, quais ainda estão rodando, quais falharam e por quê. Essa visão granular é o que normalmente exigiria instrumentação manual em pipelines de batch tradicionais — aqui ela vem de graça por cada etapa ser uma execução durável com identidade própria.
