
Olá pessoal! Após cinco anos de programação no Go, me vi bastante
um fervoroso defensor de uma abordagem específica para a configuração do programa. Neste
Vou tentar revelar suas principais idéias no artigo, bem como compartilhar uma pequena
uma biblioteca que implementa essas idéias.
Obviamente, o artigo é muito subjetivo e não pretende ser objetivo
verdades. No entanto, espero que possa ser útil à comunidade e ajudar a reduzir
tempo gasto em uma tarefa tão trivial.
Do que você está falando?
Em geral, a configuração, na minha opinião, é a definição das variáveis de nossa
programas cujos valores podemos obter de fora já em tempo de execução.
Pode ser argumentos ou parâmetros de linha de comando, variáveis de ambiente,
arquivos de configuração armazenados em disco ou em qualquer lugar da rede, tabelas de banco de dados
dados e assim por diante.
Como o Go é uma linguagem tipicamente fortemente estática, gostaríamos de
determinar e obter valores para essas variáveis, levando em consideração seu tipo.
Há um grande número de bibliotecas de código aberto ou até estruturas,
resolver esses problemas. A maioria deles representa sua própria visão.
de como fazê-lo.
Eu gostaria de falar sobre uma abordagem menos comum para a configuração do programa.
Além disso, essa abordagem me parece a mais simples.
flag
pacote
Sim, isso não é uma piada e eu realmente quero chamar sua atenção para tudo
O famoso pacote de biblioteca padrão Go.
À primeira vista, o flag
é uma ferramenta para trabalhar com parâmetros de comando
linhas e nada mais. Mas este pacote também pode ser usado como uma interface.
determinar os parâmetros do nosso programa. E no contexto da abordagem discutida
flag
usado principalmente dessa maneira.
Como mencionado acima, gostaríamos de digitar parâmetros.
O pacote flag
fornece a capacidade de fazer isso para os tipos mais básicos.
- flag.String()
, flag.Int()
e até mesmo flag.Duration()
.
Para tipos mais complexos, como []string
ou time.Time
existe uma interface
flag.Value
, que permite descrever flag.Value
obter o valor de um parâmetro a partir dele
representação de string .
Por exemplo, um parâmetro do tipo time.Time
pode ser implementado assim:
Uma propriedade importante de um pacote é sua presença na biblioteca padrão - o flag
é
maneira padrão de configurar programas , o que significa sua probabilidade
o uso entre diferentes projetos e bibliotecas é maior que outros
bibliotecas da comunidade.
Por que não usar flag
?
Parece-me que outras bibliotecas são usadas e existem por duas razões:
- Os parâmetros são lidos não apenas na linha de comando
- Eu quero estruturar os parâmetros
Se ler parâmetros, por exemplo, de arquivos, tudo fica mais ou menos claro (sobre
mais tarde), então sobre parâmetros estruturais, vale a pena dizer algumas palavras diretamente
agora
Na minha opinião, não existe a melhor maneira de determinar a configuração
programas como estruturas cujos campos poderiam ser outras estruturas e assim
além disso:
type AppConfig struct { Port int Database struct { Endpoint string Timeout time.Duration } ... }
E parece-me que é por isso que as bibliotecas são usadas e existem
estruturas que permitem trabalhar com a configuração dessa maneira.
Acho que o flag
não deve fornecer recursos de configuração estrutural.
Isso pode ser facilmente alcançado com algumas linhas de código (ou uma biblioteca
flagutil
, discutido abaixo).
Além disso, se você pensar bem, a existência de tal estrutura leva a uma forte
conectividade entre os componentes utilizados.
Configuração estrutural
A idéia é definir parâmetros independentemente da estrutura
programas e o mais próximo possível do local onde são usados - ou seja,
diretamente no nível do pacote.
Suponha que tenhamos uma implementação de cliente para algum serviço (banco de dados,
API ou o que for) chamado yoogle
:
package yoogle type Config struct { Endpoint string Timeout time.Duration } func New(c *Config) *Client {
Para preencher a estrutura yoogle.Config
, precisamos de uma função que
registra os campos de estrutura no *flag.FlagSet
recebido.FlagSet.
Essa função pode ser declarada no yoogle
pacote yoogle
ou em um pacote
yooglecfg
(no caso de uma biblioteca de terceiros, podemos escrever essa função
em outros lugares):
package yooglecfg import ( "flag" "app/yoogle" ) func Export(flag *flag.FlagSet) *yoogle.Config { var c yoogle.Config flag.StringVar(&c.Endpoint, "endpoint", "https://example.com", "endpoint for our API", ) flag.DurationVar(&c.Timeout, "timeout", time.Second, "timeout for operations", ) return &c }
Para eliminar a dependência do pacote flag
, você pode definir uma interface com
métodos flag.FlagSet
necessários:
package yooglecfg import "app/yoogle" type FlagSet interface { StringVar(p *string, name, value, desc string) } func Export(flag FlagSet) *yoogle.Config { var c yoogle.Config flag.StringVar(&c.Endpoint, "endpoint", "https://example.com", "endpoint for our API", ) return &c }
E se a configuração depende dos valores dos parâmetros (por exemplo, entre os parâmetros
o algoritmo de algo está indicado), a função yooglecfg.Export()
pode retornar
função construtora a ser chamada após analisar todos os valores
configurações:
package yooglecfg import "app/yoogle" type FlagSet interface { StringVar(p *string, name, value, desc string) } func Export(flag FlagSet) func() *yoogle.Config { var algorithm string flag.StringVar(&algorithm, "algorithm", "quick", "algorithm used to do something", ) var c yoogle.Config return func() *yoogle.Config { switch algorithm { case "quick": c.Impl = quick.New() case "merge": c.Impl = merge.New() case "bubble": panic(...) } return c } }
As funções de exportação permitem definir parâmetros de pacote sem conhecer a estrutura
configuração do programa e como obter seus valores.
Nós descobrimos uma grande estrutura de configuração e fizemos nossos parâmetros
independente, mas ainda não está claro como reuni-los e reunir
valores.
Foi para resolver esse problema que o pacote flagutil
foi gravado.
Juntando os parâmetros
Todos os parâmetros do programa, seus pacotes e bibliotecas de terceiros recebem seu prefixo
e são coletados no nível main
do pacote:
package main import ( "flag" "app/yoogle" "app/yooglecfg" "github.com/gobwas/flagutil" ) func main() { flags := flag.NewFlagSet("my-app", flag.ExitOnError) var port int flag.IntVar(&port, "port", 4050, "port to bind to", ) var config *yoogle.Config flagutil.Subset(flags, "yoogle", func(sub *flag.FlagSet) { config = yooglecfg.Export(sub) }) }
A função flagutil.Subset()
faz uma coisa simples: adiciona um prefixo
( "yoogle"
) para todos os parâmetros registrados no sub
dentro do retorno de chamada.
A execução do programa agora pode ser assim:
app -port 4050 -yoogle.endpoint https://example.com -yoogle.timeout 10s
Obter valores de parâmetro
Todos os parâmetros dentro de flag.FlagSet
contêm a implementação de flag.Value
,
que possui um método de Set(string) error
- ou seja, fornece uma oportunidade
definindo a representação de sequência do valor .
Resta ler valores de qualquer fonte na forma de pares de valores-chave e
faça uma flag.Set(key, value)
.
Isso nos dá a oportunidade de nem usar a sintaxe dos parâmetros de comando
linhas descritas no pacote flag
. Você pode analisar os argumentos, de qualquer forma,
por exemplo, como argumentos do programa posix .
package main func main() { flags := flag.NewFlagSet("my-app", flag.ExitOnError)
Assim, o arquivo config.json
pode ser assim:
{ "port": 4050, "yoogle": { "endpoint": "https://example.com", "timeout": "10s" ... } }
Conclusão
É claro que não sou o primeiro a falar dessa abordagem. Muitos
as idéias descritas acima já foram usadas de uma maneira ou de outra há vários anos, quando
Eu trabalhei na MailRu.
Assim, para simplificar a configuração de nossa aplicação e não perder tempo com
estudando (ou mesmo escrevendo) o próximo quadro de configuração é proposto
o seguinte:
- Use o
flag
como uma interface de definição de parâmetro
o programa - Exporte os parâmetros de cada pacote separadamente, sem o conhecimento da estrutura e
uma maneira de obter valores mais tarde - Defina como ler valores, prefixos e estrutura de configuração nos
main
A biblioteca flagutil
foi flagutil
meu conhecimento da biblioteca
Peterbourgon / ff - e eu não escreveria flagutil
, se não fosse por algum
discrepâncias em uso.
Obrigado pela atenção!
Referências