No desenvolvimento de software, a flexibilidade na configuração de objetos é uma necessidade comum, especialmente quando lidamos com funções e estruturas que possuem múltiplos parâmetros opcionais.
Em Go, por ser uma linguagem que não suporta sobrecarga de funções e parâmetros opcionais, encontrar uma solução elegante para esse problema pode ser desafiador. É aqui que o Functional Options Pattern entra como uma abordagem eficaz e idiomática.
O que é Functional Options Pattern?
O Functional Options Pattern é um padrão de design amplamente utilizado em Go para facilitar a configuração de estruturas complexas ou funções, principalmente quando há múltiplos parâmetros opcionais. Ele permite que você construa objetos ou configure funções de maneira flexível, utilizando funções que atuam como “opções”.
Em vez de passar diretamente todos os parâmetros para o construtor ou para a função, você cria funções separadas que aplicam configurações de maneira incremental e opcional. Essa abordagem ajuda a evitar funções com longas listas de parâmetros ou a necessidade de múltiplos construtores para diferentes casos de uso.
Aplicando na prática
Vamos considerar um problema comum no desenvolvimento de APIs: a criação de um cliente HTTP personalizado, onde algumas configurações como timeout, headers e retries, são opcionais.
Sem o Functional Options Pattern, você precisaria passar todos os parâmetros possíveis para a função de criação, o que resultaria em um código menos legível e difícil de manter.
Criando um HTTP Client com configurações opcionais
Imagine que queremos criar um cliente HTTP que permita definir um timeout opcional, headers personalizados e a quantidade de retries (tentativas de reconexão). A solução tradicional sem o Functional Options Pattern resultaria em algo assim:
type HttpClient struct {
Timeout time.Duration
Headers map[string]string
Retries int
}
func NewHttpClient(timeout time.Duration, headers map[string]string, retries int) *HttpClient {
return &HttpClient{
Timeout: timeout,
Headers: headers,
Retries: retries,
}
}
Se você não quiser definir todos os parâmetros, ainda assim teria que passar valores padrão ou zeros, o que poderia causar confusão e dificultar a legibilidade.
Agora, vamos ver como aplicar o Functional Options Pattern para melhorar essa abordagem.
Implementando o Functional Options Pattern
Aqui, usaremos funções que encapsulam as opções, permitindo que você configure apenas os parâmetros que realmente precisa.
type HttpClient struct {
Timeout time.Duration
Headers map[string]string
Retries int
}
// Option define o tipo de função que aplicará configurações ao HttpClient
type Option func(*HttpClient)
// NewHttpClient cria uma instância de HttpClient com opções funcionais
func NewHttpClient(opts ...Option) *HttpClient {
client := &HttpClient{
Timeout: 30 * time.Second, // valor padrão
Headers: make(map[string]string),
Retries: 3, // valor padrão
}
// Aplica as opções fornecidas
for _, opt := range opts {
opt(client)
}
return client
}
// WithTimeout permite configurar o timeout do cliente
func WithTimeout(timeout time.Duration) Option {
return func(c *HttpClient) {
c.Timeout = timeout
}
}
// WithHeaders permite adicionar headers personalizados
func WithHeaders(headers map[string]string) Option {
return func(c *HttpClient) {
for k, v := range headers {
c.Headers[k] = v
}
}
}
// WithRetries permite configurar o número de tentativas de reconexão
func WithRetries(retries int) Option {
return func(c *HttpClient) {
c.Retries = retries
}
}
Agora, ao criar um novo cliente HTTP, você pode especificar apenas as opções que desejar:
func main() {
client := NewHttpClient(
WithTimeout(10*time.Second),
WithHeaders(map[string]string{"Authorization": "Bearer token"}),
)
fmt.Printf("Timeout: %v, Headers: %v, Retries: %d\\\\n", client.Timeout, client.Headers, client.Retries)
}
Isso permite flexibilidade e clareza ao configurar o cliente, sem precisar passar todos os parâmetros sempre.
Packages que utilizam esse padrão
Diversos packages populares no ecossistema Go utilizam o Functional Options Pattern. Alguns exemplos notáveis incluem:
grpc-go: O package de Go para gRPC usa Functional Options para configurar servidores e clientes gRPC.zap: O famoso logger de alta performance utiliza esse padrão para configurar várias opções de logging, como o nível de log, formato de saída e mais.
Conclusão
O Functional Options Pattern é uma solução poderosa e idiomática em Go para lidar com funções ou estruturas que exigem flexibilidade na configuração. Ao adotar esse padrão, você melhora a legibilidade do código, oferece flexibilidade para usuários finais e evita a proliferação de funções com listas extensas de parâmetros.
Seja criando um cliente HTTP personalizado ou configurando uma biblioteca complexa, o Functional Options Pattern é uma ferramenta valiosa no design de APIs em Go.
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.




