
Hola a todos! Después de cinco años de programación en Go, me encontré bastante
un ferviente defensor de un enfoque específico para la configuración del programa. En este
Trataré de revelar sus ideas principales en el artículo, así como compartir un pequeño
Una biblioteca que implementa estas ideas.
Obviamente, el artículo es muy subjetivo y no pretende ser objetivo.
verdades Sin embargo, espero que pueda ser útil para la comunidad y ayudar a reducir
tiempo dedicado a una tarea tan trivial.
De que estas hablando
En general, la configuración, en mi opinión, es la definición de las variables de nuestro
programas cuyos valores podemos obtener desde afuera ya en tiempo de ejecución.
Pueden ser argumentos o parámetros de línea de comando, variables de entorno,
archivos de configuración almacenados en el disco o en cualquier lugar de la red, tablas de bases de datos
datos y así sucesivamente.
Como Go es un lenguaje tipeado muy estático, nos gustaría
determinar y obtener valores para tales variables, teniendo en cuenta su tipo.
Hay una gran cantidad de bibliotecas de código abierto o incluso marcos,
resolviendo tales problemas. La mayoría de ellos representan su propia visión.
de cómo hacerlo.
Me gustaría hablar sobre un enfoque menos común para la configuración del programa.
Además, este enfoque me parece el más simple.
Paquete de la flag
Sí, esto no es una broma y realmente quiero llamar su atención sobre todo
El famoso paquete de biblioteca estándar Go.
A primera vista, flag
es una herramienta para trabajar con parámetros de comando
líneas y no más. Pero este paquete también se puede usar como una interfaz.
determinando los parámetros de nuestro programa. Y en el contexto del enfoque discutido
flag
usa principalmente de esa manera.
Como se mencionó anteriormente, nos gustaría tener parámetros escritos.
El paquete de flag
proporciona la capacidad de hacer esto para la mayoría de los tipos básicos.
- flag.String()
, flag.Int()
e incluso flag.Duration()
.
Para tipos más complejos, como []string
o tiempo. time.Time
hay una interfaz
flag.Value
, que le permite describir flag.Value
obtener el valor de un parámetro
Representación de cadena .
Por ejemplo, un parámetro de tipo time.Time
puede implementarse así:
Una propiedad importante de un paquete es su presencia en la biblioteca estándar : el flag
es
forma estándar de configurar programas , lo que significa su probabilidad
El uso entre diferentes proyectos y bibliotecas es mayor que otros
bibliotecas en la comunidad.
¿Por qué no usar la flag
?
Me parece que otras bibliotecas se usan y existen por dos razones:
- Los parámetros se leen no solo desde la línea de comando
- Quiero estructurar los parametros
Si lee parámetros, por ejemplo, de archivos, todo está más o menos claro (aproximadamente
esto más adelante), luego, sobre los parámetros estructurales, vale la pena decir algunas palabras directamente
ahora
En mi opinión, no hay la mejor manera de determinar la configuración
programas como estructuras cuyos campos podrían ser otras estructuras y así
además:
type AppConfig struct { Port int Database struct { Endpoint string Timeout time.Duration } ... }
Y me parece que es por eso que las bibliotecas se usan y existen
marcos que le permiten trabajar con la configuración de esa manera.
Creo que flag
no debería proporcionar capacidades de configuración estructural.
Esto se puede lograr fácilmente con unas pocas líneas de código (o una biblioteca
flagutil
, que se analiza a continuación).
Además, si lo piensas, la existencia de tal estructura conduce a una fuerte
conectividad entre los componentes utilizados.
Configuración estructural
La idea es definir parámetros independientemente de la estructura
programas y lo más cerca posible del lugar donde se usan, es decir,
directamente a nivel de paquete.
Supongamos que tenemos una implementación de cliente para algún servicio (base de datos,
API o lo que sea) llamado yoogle
:
package yoogle type Config struct { Endpoint string Timeout time.Duration } func New(c *Config) *Client {
Para llenar la estructura yoogle.Config
, necesitamos una función que
registra los campos de estructura en el *flag.FlagSet
recibido.
Dicha función se puede declarar a yoogle
paquete de yoogle
o en un paquete
yooglecfg
(en el caso de una biblioteca de terceros, podemos escribir dicha función
en otro lugar):
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 la dependencia del paquete de flag
, puede definir una interfaz con
flag.FlagSet
necesaria Métodos de conjunto de flag.FlagSet
:
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 }
Y si la configuración depende de los valores de los parámetros (por ejemplo, entre los parámetros
se indica el algoritmo de algo), la función yooglecfg.Export()
puede devolver
función de constructor que se llamará después de analizar todos los valores
configuraciones:
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 } }
Las funciones de exportación le permiten definir los parámetros del paquete sin conocer la estructura
configuración del programa y cómo obtener sus valores.
Descubrimos una gran estructura de configuración e hicimos nuestros parámetros
independiente, pero aún no está claro cómo reunirlos a todos y obtener
valores
Fue para resolver este problema que se flagutil
paquete flagutil
.
Poniendo los parámetros juntos
Todos los parámetros del programa, sus paquetes y bibliotecas de terceros reciben su prefijo
y se recopilan en el nivel de paquete main
:
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) }) }
La función flagutil.Subset()
hace algo simple: agrega un prefijo
( "yoogle"
) a todos los parámetros registrados en sub
dentro de la devolución de llamada.
Ejecutar el programa ahora puede verse así:
app -port 4050 -yoogle.endpoint https://example.com -yoogle.timeout 10s
Obtener valores de parámetros
Todos los parámetros dentro de flag.FlagSet
contienen la implementación de flag.Value
,
que tiene un método de Set(string) error
, es decir, proporciona una oportunidad
establecer la representación de cadena del valor .
Queda por leer los valores de cualquier fuente en forma de pares clave-valor y
hacer una flag.Set(key, value)
.
Esto nos da la oportunidad de ni siquiera usar la sintaxis de los parámetros del comando
líneas descritas en el paquete de flag
. Puede analizar los argumentos, de cualquier manera,
por ejemplo, como argumentos del programa posix .
package main func main() { flags := flag.NewFlagSet("my-app", flag.ExitOnError)
En consecuencia, el archivo config.json
puede verse así:
{ "port": 4050, "yoogle": { "endpoint": "https://example.com", "timeout": "10s" ... } }
Conclusión
Por supuesto, no soy el primero en hablar de tal enfoque. Muchos de
Las ideas descritas anteriormente ya se utilizaron de una forma u otra hace varios años, cuando
Trabajé en MailRu.
Entonces, para simplificar la configuración de nuestra aplicación y no perder tiempo en
estudiar (o incluso escribir) se propone el siguiente marco de configuración
lo siguiente:
- Use el
flag
como una interfaz de definición de parámetros
el programa - Exportar los parámetros de cada paquete por separado, sin conocimiento de la estructura y
una forma de obtener valores más tarde - Definir cómo leer valores, prefijos y estructura de configuración en
main
La biblioteca flagutil
se flagutil
mi conocimiento de la biblioteca.
Peterbourgon / ff - y no escribiría flagutil
, si no fuera por algunos
discrepancias en el uso.
Gracias por su atencion!
Referencias