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) T ou func(T) (T, error) ) para o formato Step[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:
-
TrimSpace
para remover espaços -
ToUpper
para 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 (0)