Конфигурация сервера
ВСем привет
Изучаю го и до сих пор конфигурировал сервер, как это показывают во всех мануалах(в некоторых источника такой метод называют "хардкодить значения"). То есть описывал роутеры, сервер и пр. не в отдельных файлах конфига, а в функциях.
Пример такой конфигурации сервера здесь:
Но вот начал смотреть видеоролики на ютубе по rest api и автор показывает несколько роликов как скофигурировать сервер другим способом.
Здесь я скидываю весь репозиторий, так как такая конфигурация предполагает, что будут созданы разные config файлы, makefile, разные пакеты, в которых так же важные для конфигурации сервера данные.
Скажу мое личное мнение: Способ2 кажется излишне запутанным. При изменениях, казалось бы, несвязанных между собой файлов, структур, переменных, ломается весь проект. (хотя такое мнения возможно сформированно болью моего мозга при восприятии нового)
Способ1 используется во всех манулах, что мне попадались и в проектах на гитхабе я встречаю как способ1, так и способ2.
Прошу публику разъянить мне, каким способом лучше пользоваться(каким спосбом чаще пользуются в проде).
П.С: репозитории не мои, просто набрел, когда самостоятельно пытался понять, какой метод чаще используют
Ответы (1 шт):
Я за один конфигурационный файл в линуксе и за один ключ в реестре Windows.
В случае линукса конфиг я бы положил в ~/.config/<servername>/config.yaml. В гитхабе много пакетов для работы с YAML. Я предпочитаю YAML, так как он позволяет легко создавать многоуровневые конфиги и похож на мой любимый Python.
Кроме конфига из ~/.config/ я бы добавил в командную строку параметр --config и -C для задания специализированного конфигурационного файла.
В реестре Windows я бы использовал ту же структуру что и YAML конфиг.
Конкретный язык конфигов не принципиален. Были проекты с TOML, были с java properties, с ini файлами, даже с XML. Во всех случаях я предпочитаю единый конфиг. Были опыты с включением конфигов (типа include), но это капец как неудобно.
Пример приложения с конфигурационным файлом
Пример sample_app печатает приветствие пользователю. Текст приветствия и имя пользователя указаны в конфигурационном файле, их можно переопределить флагами командной строки --greeting и --user
Конфигурационный файл YAML выглядит так:
greeting: Listen to me
user: world
Путь к конфигурационному файлу по умолчанию:
- в Linux:
$HOME/.config/SampleVendor/SampleApp/config.yaml - в Windows:
%UserProfile%\AppData\Roaming\SampleVendor\SampleApp\config.yaml - в Mac:
$HOME/Library/Application Support/SampleVendor/SampleApp/config.yaml
Для определения путей я использую пакет https://github.com/shibukawa/configdir
Для создания командной строки я обычно пользуюсь пакетом https://github.com/spf13/cobra/
Для обработки конфигурационного файла и параметров командной строки использую пакет https://github.com/spf13/viper
Приложение запускается так:
./sample_appбез параметров печатает документацию как запускать приложение./sample_app help [команда]печатает документацию как запускать конкретную команду. Например,./sample_app help helloрасскажет, как запускать./sample_app hello./sample_app helloпечататает приветствие, используя параметры из конфигурационного файла и опций командной строки
package main
import (
"errors"
"fmt"
"os"
"path"
"github.com/shibukawa/configdir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)
// Global params
const (
VENDOR_NAME = "SampleVendor"
APP_NAME = "SampleApp"
CONFIG_FLAG = "config"
CONFIG_FLAG_SHORT = "C"
CONFIG_FILE_NAME = "config.yaml"
)
// Params of `hello` command
const (
GREETING_KEY = "greeting" // Key in the config file
GREETING_FLAG = "greeting" // Command line option name
USER_KEY = "user" // Key in the config file
USER_FLAG = "user" // Command line option name
)
var (
defaultConfigDir *configdir.Config
defaultConfigPath string
configPath string
DEFAULT_CONFIG = map[string]string{
GREETING_KEY: "Hello",
USER_KEY: "John Dow",
}
)
var (
// Root command of the application
rootCmd = &cobra.Command{
Use: "sample_app",
Short: "sample_app shows how to use config files in Go",
}
// `hello` command of the application
helloCmd = &cobra.Command{
Use: "hello",
Short: "Prints greeting message",
Long: `Prints greeting message
Takes greeting and user name from config file.
Config file settings could be overwritten by the command line options.`,
Example: ` sample_app hello --user 'world' --greeting 'Hello'
prints 'Hello, world'`,
PreRunE: setup_hello,
RunE: hello,
}
)
// Setup the commands
func init() {
// Setup rootCmd
configCfg := configdir.New(VENDOR_NAME, APP_NAME)
configDirs := configCfg.QueryFolders(configdir.Global)
if len(configDirs) == 0 {
panic("No configuration paths")
}
defaultConfigDir = configDirs[0]
defaultConfigPath = path.Join(defaultConfigDir.Path, CONFIG_FILE_NAME)
rootCmd.PersistentFlags().StringVarP(&configPath, CONFIG_FLAG, CONFIG_FLAG_SHORT,
"",
"Overwrites user-level config file, default is '"+defaultConfigPath+"'",
)
// Setup helloCmd
helloCmd.Flags().String(GREETING_FLAG, "", "Greeting part of the message, overwrites '"+GREETING_KEY+"' key of the config file")
helloCmd.Flags().String(USER_FLAG, "", "User part of the message, overwrites '"+USER_KEY+"' key of the config file")
rootCmd.AddCommand(helloCmd)
cobra.OnInitialize(loadConfig)
}
func ensureDefaultConfig() {
if err := defaultConfigDir.MkdirAll(); err != nil {
panic("Failed to create configuration folder")
}
if !defaultConfigDir.Exists(CONFIG_FILE_NAME) {
_, err := defaultConfigDir.Create(CONFIG_FILE_NAME)
if err != nil {
panic("Failed to create default config: " + err.Error())
}
defaultCfgData, err := yaml.Marshal(DEFAULT_CONFIG)
if err != nil {
panic("Must never happen")
}
// Write default contents
if err := defaultConfigDir.WriteFile(CONFIG_FILE_NAME, defaultCfgData); err != nil {
panic("Failed to create default config: " + err.Error())
}
}
}
func loadConfig() {
if configPath != "" {
if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) {
panic("Config faile doesn't exist: " + configPath)
}
} else {
ensureDefaultConfig()
configPath = defaultConfigPath
}
viper.SetConfigFile(configPath)
if err := viper.ReadInConfig(); err != nil {
panic("Failed to load config file " + viper.ConfigFileUsed() + ": " + err.Error())
}
}
func setup_hello(cmd *cobra.Command, args []string) error {
viper.BindPFlag(GREETING_KEY, cmd.Flags().Lookup(GREETING_FLAG))
viper.BindPFlag(USER_KEY, cmd.Flags().Lookup(USER_FLAG))
return nil
}
func hello(cmd *cobra.Command, args []string) error {
if !viper.IsSet("greeting") {
return fmt.Errorf("Greeting is not set. Either use `--greeting` flag or 'greeting' key in the config file")
}
if !viper.IsSet("user") {
return fmt.Errorf("Greeting is not set. Either use `--user` flag or 'user' key in the config file")
}
greeting := viper.GetString("greeting")
user := viper.GetString("user")
msg := greeting + ", " + user
println(msg)
return nil
}
func main() {
rootCmd.Execute()
}