Introdução
Recentemente embarcamos em um pequeno projeto interno com o colega Jonas Freire: criar uma forma mais expressiva e limpa de encadear operações em Go. Eu participei ajudando a definir contratos, escrever testes e validar o comportamento do pacote. Em conjunto com a empresa, decidimos abrir esse trabalho como open source, para que outros desenvolvedores também possam usar, contribuir e nos dar feedback.
Esse artigo vai mostrar, de forma simples e prática, como usar o pacote pipeline, seus conceitos básicos e alguns exemplos reais para você começar rapidamente.
Por que usar pipeline?
Em Go, quando você precisa realizar várias transformações ou validações consecutivas, o código costuma ficar assim:
res, err := foo()
if err != nil { return err }
res, err = bar(res)
if err != nil { return err }
res, err = baz(res)
if err != nil { return err }
// ...
Ou, se for uma cadeia de funções, você acaba fazendo algo como:
out := fn3(fn2(fn1(input)))
Isso torna o fluxo difícil de ler e difícil de inserir tratamento de erro de forma padronizada.
O pipeline deste projeto provê um jeito idiomático de criar uma sequência de operações que:
- Recebe um valor inicial
- Aplica passos (funções) um após o outro
- Interrompe a execução no primeiro erro
- Retorna o valor final ou o erro ocorrido
Ou seja: deixa o encadeamento linear, evita repetição de checagem de erro e melhora a legibilidade.
Conceitos principais
Aqui estão os conceitos centrais que você precisa entender para usar o pipeline:
| Conceito | Descrição | 
|---|---|
| Step[T] | É um tipo de função que representa um passo da pipeline: func(ctx context.Context, T) (T, error) | 
| Pipeline[T] | O construtor da sequência de passos, começando com um valor inicial de tipo T | 
| New(initial T) | Cria uma nova pipeline com valor inicial | 
| Do(step Step[T]) | Adiciona mais um passo à pipeline | 
| Execute() / ExecuteWithContext(ctx) | Executa todos os passos até o fim ou até encontrar erro | 
| ToStep(...) | Função auxiliar que converte funções “normais” (por exemplo func(T) Toufunc(T) (T, error)) para o formatoStep[T] | 
O pipeline também incorpora suporte a contexto, para cancelamento ou timeout, e retorna erros enriquecidos com informações como em que passo ocorreu a falha.
Como usar (exemplos práticos)
Exemplo básico com strings
import (
    "context"
    "fmt"
    "strings"
    "github.com/lastro-co/pipeline"
)
func ensureNonEmpty(_ context.Context, s string) (string, error) {
    if s == "" {
        return "", fmt.Errorf("string vazia")
    }
    return s, nil
}
func main() {
    out, err := pipeline.
        New("  ola mundo  ").
        Do(pipeline.ToStep(strings.TrimSpace)).   // converte func(string) string
        Do(pipeline.ToStep(strings.ToUpper)).     // outra função simples
        Do(ensureNonEmpty).                       // função já no formato Step[T]
        Execute()
    if err != nil {
        fmt.Println("Erro:", err)
    } else {
        fmt.Println("Resultado:", out)
    }
}
Aqui estamos compondo 3 passos:
- 
TrimSpacepara remover espaços
- 
ToUpperpara deixar em maiúsculo
- Validar que não ficou vazio
Se em algum passo houver erro (por exemplo valor vazio), a execução para ali e retorna o erro.
Adaptando funções com múltiplos parâmetros
Se você já tiver uma função como:
func add(x, y int) int { return x + y }
Você pode adaptá-la dentro da pipeline usando uma closure:
.Do(func(ctx context.Context, x int) (int, error) {
    return add(x, 5), nil
})
Assim você encapsula a lógica e transforma em um Step[int].
Uso de contexto / timeout
Você pode usar ExecuteWithContext para que a pipeline respeite cancelamento ou timeout:
ctx, cancel := context.WithTimeout(context.Background(), 100 * time.Millisecond)
defer cancel()
result, err := pipeline.
    New("algum valor").
    Do(pipeline.ToStep(strings.ToUpper)).
    Do(slowStep).   // passo que demora
    ExecuteWithContext(ctx)
if err != nil {
    // Por exemplo: context.DeadlineExceeded
    fmt.Println("Falha:", err)
}
Se o contexto for encerrado antes do término, o pipeline interrompe e retorna o erro do contexto.
Erros com contexto de passo
Quando um passo falha, o pacote retorna um erro enriquecido (PipelineError) que contém:
- 
StepIndex: índice do passo que falhou
- 
TotalSteps: número total de passos
- 
OriginalErr: o erro retornado pela função que falhou
- 
LastValue: último valor processado até então
Isso ajuda a depurar exatamente onde a falha ocorreu.
Dicas boas práticas & considerações
- Prefira criar passos pequenos, com responsabilidade única.
- Use ToStep(...)sempre que possível para converter funções existentes e evitar boilerplate.
- Teste cada passo isoladamente para garantir que o comportamento de erro está correto.
- Para operações que recebem múltiplos parâmetros, use closures como mostrado acima.
- Em casos especiais, você pode usar tipos de erro customizados para identificar falhas específicas e tratá-las de forma distinta após Execute.
Conclusão e convite à comunidade
Esse pacote surgiu como uma iniciativa interna do Jonas (e com minha ajuda nos contratos e testes) e agora está aberto como open source. A ideia é oferecer uma forma elegante e clara de encadear operações em Go, com tratamento automático de erros e suporte a contexto.
Se você gostou e quiser contribuir — seja com sugestões, documentação, exemplos ou novos recursos — fique à vontade para abrir issues, pull requests ou me chamar. Vamos crescer juntos! 🚀
 

 
    
Top comments (2)
Magnífica dica, Renan! Obrigada por contribuir!
Show, fico feliz que estou ajudando de alguma forma. Qualquer coisa pode me chamar Isa.