Конфигурация сервера

ВСем привет

Изучаю го и до сих пор конфигурировал сервер, как это показывают во всех мануалах(в некоторых источника такой метод называют "хардкодить значения"). То есть описывал роутеры, сервер и пр. не в отдельных файлах конфига, а в функциях.

Пример такой конфигурации сервера здесь:

Способ1.

Но вот начал смотреть видеоролики на ютубе по rest api и автор показывает несколько роликов как скофигурировать сервер другим способом.

Способ2.

Здесь я скидываю весь репозиторий, так как такая конфигурация предполагает, что будут созданы разные config файлы, makefile, разные пакеты, в которых так же важные для конфигурации сервера данные.

Скажу мое личное мнение: Способ2 кажется излишне запутанным. При изменениях, казалось бы, несвязанных между собой файлов, структур, переменных, ломается весь проект. (хотя такое мнения возможно сформированно болью моего мозга при восприятии нового)

Способ1 используется во всех манулах, что мне попадались и в проектах на гитхабе я встречаю как способ1, так и способ2.

Прошу публику разъянить мне, каким способом лучше пользоваться(каким спосбом чаще пользуются в проде).

П.С: репозитории не мои, просто набрел, когда самостоятельно пытался понять, какой метод чаще используют


Ответы (1 шт):

Автор решения: Pak Uula

Я за один конфигурационный файл в линуксе и за один ключ в реестре 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()
}
→ Ссылка