Skip to content

Viper

저장소 주소: spf13/viper: Go configuration with fangs (github.com)

문서 주소: spf13/viper: Go configuration with fangs (github.com)

TIP

공식적으로 Viper2 로의 전환을 논의 중이며 관심 있는 분은 확인해보세요: Viper2

설치

go get github.com/spf13/viper

소개

Viper 는 Go 애플리케이션을 위한 완전한 구성 파일 솔루션으로 거의 모든 유형의 구성 요구 사항과 형식을 처리할 수 있으며 프로젝트의 구성 파일을 쉽게 관리할 수 있고 다음과 같은 특징이 있습니다.

  • 기본값 설정
  • JSON, TOML, YAML, HCL, envfile, Java properties 형식 지원
  • 구성 파일 실시간 모니터링 및 다시 로드 지원
  • 환경 변수에서 읽기 지원
  • 원격 구성 시스템에서 구성 읽기 및 변경 모니터링 지원
  • 커맨드라인 플래그 읽기 지원
  • 버퍼 읽기 지원
  • 명시적 값 설정 지원

공식적으로 Viper 는 모든 애플리케이션 구성 요구 사항을 충족할 수 있다고 하며 개발자는 애플리케이션 구축에만 집중하고 Viper 가 구성 관리를 담당하면 된다고 합니다. 많은 유명한 프로젝트가 Viper 를 사용합니다.

DANGER

Viper 는 구성 파일의 암호화 및 복호화를 담당하지 않습니다. 즉 구성 파일에 대한 보안 처리를 하지 않습니다.

읽기 순서

Viper 는 다음 우선 순위를 사용하여 구성을 읽습니다.

  1. 명시적으로 설정된 값
  2. 커맨드라인 플래그
  3. 환경 변수
  4. 구성 파일
  5. 키 - 값 저장소
  6. 기본값

TIP

Viper 구성의 키는 대소문자를 구분하지 않으며 향후 논의에서 이를 선택 사항으로 만들 수 있습니다.

기본값

양호한 구성 시스템은 기본값 설정을 지원해야 하며 때로는 반드시 필요하지는 않지만 구성 파일이 설정되지 않은 경우 매우 유용합니다. 다음은 예제입니다.

go
viper.SetDefault("filePath","./dir/img/usr")
viper.SetDefault("root","123456")

구성 파일 읽기

Viper 는 최소한의 구성만으로 구성 파일을 어디에서 찾아야 하는지 알 수 있습니다. Viper 는 JSON, TOML, YAML, HCL, INI, envfile 및 JavaProperties 파일을 지원합니다. Viper 는 여러 경로를 동시에 검색할 수 있지만 현재 단일 Viper 인스턴스는 단일 구성 파일만 지원합니다. Viper 는 검색 경로를 기본적으로 구성하지 않으며 기본 결정을 애플리케이션에 맡깁니다.

다음은 Viper 를 사용하여 구성 파일을 읽는 예제로 전체 경로를 지정할 필요는 없지만 사용할 때 최소한 하나의 구성 파일을 제공해야 합니다.

go
func TestReadConfigFile(t *testing.T) {
   viper.SetConfigName("config.yml") // config 라는 이름의 구성 파일 읽기, 특정 파일 확장자명 설정 안 함
   viper.SetConfigType("yaml")       // 특정 파일 확장자명을 설정하지 않을 때 파일 유형을 지정해야 함
   viper.AddConfigPath("./")         // 현재 폴더에서 찾기
   viper.AddConfigPath("$HOME/")     // 변수 사용
   viper.AddConfigPath(".")          // 작업 디렉토리에서 찾기
   err := viper.ReadInConfig() // 구성 읽기
   if err != nil {
      log.Fatalln(err)
   }
}

구성 파일을 찾지 못한 경우를 별도로 처리할 수도 있습니다.

go
if err := viper.ReadInConfig(); err != nil {
  if _, ok := err.(viper.ConfigFileNotFoundError); ok {
    // 구성 파일을 찾지 못함
  } else {
    // 기타 유형의 오류
  }
}

다음은 구성에 액세스하는 모든 함수입니다.

  • Get(key string) : interface{}
  • GetBool(key string) : bool
  • GetFloat64(key string) : float64
  • GetInt(key string) : int
  • GetIntSlice(key string) : []int
  • GetString(key string) : string
  • GetStringMap(key string) : map[string]interface{}
  • GetStringMapString(key string) : map[string]string
  • GetStringSlice(key string) : []string
  • GetTime(key string) : time.Time
  • GetDuration(key string) : time.Duration
  • IsSet(key string) : bool
  • AllSettings() : map[string]interface{}

중첩 구성에 액세스할 때는 . 구분 기호를 사용하여 액세스합니다. 예를 들어:

{
  "server":{
    "database":{
      "url": "mysql...."
    }
  }
}

GetString("server.database.url") 를 통해 중첩 액세스할 수 있습니다.

구성 파일에 쓰기

Viper 는 런타임에 저장된 구성을 구성 파일에 쓰기 위해 개발자가 사용할 수 있는 일련의 함수를 제공합니다.

go
// WriteConfig 구성을 원래 구성 파일에 쓰며 존재하지 않으면 오류가 발생하고 존재하면 덮어씁니다.
func WriteConfig() error { return v.WriteConfig() }

// SafeWriteConfig 구성을 안전하게 원래 구성 파일에 쓰며 존재하지 않으면 쓰고 존재하면 덮어쓰지 않습니다.
func SafeWriteConfig() error { return v.SafeWriteConfig() }

// WriteConfigAs 현재 구성을 지정된 파일에 쓰며 파일이 존재하지 않으면 오류를 반환하고 존재하면 기존 구성을 덮어씁니다.
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }

// SafeWriteConfigAs 지정된 파일이 존재하면 원래 구성 파일을 덮어쓰지 않으며 파일이 존재하면 오류를 반환합니다.
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }

다음은 일부 예제입니다.

go
func TestWritingConfig(t *testing.T) {
   viper.WriteConfig() // 구성을 원래 구성 파일에 쓰며 이 구성 파일들은 미리 'viper.AddConfigPath()' 와 'viper.SetConfigName' 으로 정의되어야 함
   viper.SafeWriteConfig()
   viper.WriteConfigAs("/path/to/my/.config")
   viper.SafeWriteConfigAs("/path/to/my/.config") // 지정된 파일이 존재하므로 오류가 반환됨
   viper.SafeWriteConfigAs("/path/to/my/.other_config")
}

모니터링 및 다시 로드

Viper 는 애플리케이션이 런타임에 동적으로 구성 파일을 읽을 수 있게 합니다. 즉 애플리케이션을 다시 시작하지 않아도 업데이트된 구성을 적용할 수 있으며 모든 변경 사항을 놓치지 않습니다. Viper 인스턴스에 구성 변경을 감시하도록 간단히 지시하거나 변경이 발생할 때마다 실행할 함수를 viper 에 제공할 수 있습니다.

go
func TestWatchingConfig(t *testing.T) {
  viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("구성 파일이 변경됨:", e.Name)
  })
  viper.WatchConfig()
}

별칭

go
func TestAliases(t *testing.T) {
   viper.RegisterAlias("a", "b")
   viper.Set("a", 1)
   viper.Set("b", 2) // a 의 구성을 덮어씀
   fmt.Println(viper.GetInt("a"))
}

하위 구조 추출

앞서 . 구분 기호를 통해 중첩 구성에 액세스하는 방법을 언급했는데 실제로는 viper.Sub() 함수를 통해 하위 구조를 추출할 수도 있으며 반환 값은 Viper 인스턴스입니다. 다음은 예제입니다.

yaml
cache:
  cache1:
    max-items: 100
    item-size: 64
  cache2:
    max-items: 200
    item-size: 80
go
cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // 존재하지 않으면 nil 반환
  panic("cache1 구성이 존재하지 않음")
}

중첩 구분 기호 설정

지정한 키에 . 이 포함되기를 원할 때는 오해석을 방지하기 위해 수동으로 다른 구분 기호를 지정해야 합니다. 예를 들어:

go
viper.KeyDelimiter("/") // 구분 기호를 / 로 설정

역직렬화

Viper 는 구성을 구조체나 map 으로 역직렬화하는 두 가지 함수를 제공하며 중첩 구조도 지원합니다.

  • Unmarshal(rawVal interface{}) : error
  • UnmarshalKey(key string, rawVal interface{}) : error
go
type config struct {
  Port int
  Name string
  PathMap string `mapstructure:"path_map"`
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
  t.Fatalf("구조체로 역직렬화할 수 없음, %v", err)
}

직렬화

현재 구성을 특정 형식으로 직렬화하여 문자열로 만들어 구성 파일에 저장할 수 있도록 합니다. 일반적으로 JSON, TOML, YAML, HCL, envfile, Java properties 를 지원합니다.

TIP

Viper 는 사용자 정의 직렬화 형식도 지원합니다. Decoding custom formats with Viper - Márk Sági-Kazár (sagikazarmark.hu)

go
import (
  yaml "gopkg.in/yaml.v2"
  // ...
)

func yamlStringSettings() string {
  c := viper.AllSettings()
  bs, err := yaml.Marshal(c)
  if err != nil {
    log.Fatalf("구성을 YAML 로 직렬화할 수 없음: %v", err)
  }
  return string(bs)
}

여러 인스턴스

일반적으로 Viper 에서 제공하는 전역 인스턴스만으로 충분하지만 하나의 인스턴스는 하나의 구성 파일만 매핑할 수 있으므로 더 많은 작업을 수행하기 위해 여러 인스턴스를 직접 생성할 수 있습니다. 예를 들어:

go
x := viper.New()
y := viper.New()

x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")

//...

Golang by www.golangdev.cn edit