Skip to content

Viper

Địa chỉ kho lưu trữ: spf13/viper: Go configuration with fangs (github.com)

Địa chỉ tài liệu: spf13/viper: Go configuration with fangs (github.com)

TIP

Đang có thảo luận về việc chuyển đổi sang Viper2, những người quan tâm có thể tìm hiểu: Viper2

Cài đặt

go get github.com/spf13/viper

Giới thiệu

Viper, là một giải pháp file cấu hình hoàn chỉnh cho các ứng dụng Go, có thể xử lý hầu hết mọi loại yêu cầu và định dạng cấu hình, thuận tiện quản lý file cấu hình của dự án và có các tính năng đặc sắc sau:

  • Thiết lập giá trị mặc định
  • Hỗ trợ định dạng JSON, TOML, YAML, HCL, envfile, Java properties
  • Hỗ trợ giám sát và tải lại file cấu hình theo thời gian thực
  • Hỗ trợ đọc từ biến môi trường
  • Hỗ trợ đọc cấu hình từ hệ thống cấu hình từ xa và giám sát thay đổi
  • Hỗ trợ đọc cờ dòng lệnh
  • Hỗ trợ đọc từ bộ đệm
  • Hỗ trợ hiển thị giá trị đã đặt

Chính thức tuyên bố Viper có thể đáp ứng mọi yêu cầu cấu hình của ứng dụng, nhà phát triển chỉ cần tập trung vào việc xây dựng ứng dụng, để Viper chịu trách nhiệm quản lý cấu hình, nhiều dự án nổi tiếng đều sử dụng Viper

DANGER

Viper không chịu trách nhiệm mã hóa và giải mã file cấu hình, nghĩa là không thực hiện bất kỳ xử lý bảo mật nào đối với file cấu hình.

Thứ tự đọc

Viper sử dụng thứ tự ưu tiên sau để đọc cấu hình:

  1. Giá trị được đặt rõ ràng
  2. Cờ dòng lệnh
  3. Biến môi trường
  4. File cấu hình
  5. Lưu trữ khóa-giá trị
  6. Giá trị mặc định

TIP

Khóa trong cấu hình Viper không phân biệt chữ hoa chữ thường, các cuộc thảo luận tiếp theo có thể biến điều này thành tùy chọn.

Giá trị mặc định

Một hệ thống cấu hình tốt nên hỗ trợ thiết lập giá trị mặc định, mặc dù đôi khi không nhất thiết phải cần, nhưng sẽ rất hữu ích khi không có file cấu hình được đặt, dưới đây là một ví dụ.

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

Đọc file cấu hình

Viper chỉ cần rất ít cấu hình là có thể biết nơi tìm kiếm file cấu hình. Viper hỗ trợ JSON, TOML, YAML, HCL, INI, envfile và file Java Properties. Viper có thể tìm kiếm nhiều đường dẫn cùng lúc, nhưng hiện tại một phiên bản Viper duy nhất chỉ hỗ trợ một file cấu hình duy nhất. Viper không mặc định cấu hình đường dẫn tìm kiếm, để quyết định mặc định cho ứng dụng.

Dưới đây là một ví dụ về việc sử dụng Viper để đọc file cấu hình, không cần chỉ định một đường dẫn đầy đủ, nhưng khi sử dụng ít nhất nên cung cấp một file cấu hình.

go
func TestReadConfigFile(t *testing.T) {
   viper.SetConfigName("config.yml") // Đọc file cấu hình tên config, không đặt hậu tố file cụ thể
   viper.SetConfigType("yaml")       // Khi không đặt hậu tố file cụ thể, bắt buộc phải chỉ định loại file
   viper.AddConfigPath("./")         // Tìm kiếm trong thư mục hiện tại
   viper.AddConfigPath("$HOME/")     // Sử dụng biến
   viper.AddConfigPath(".")          // Tìm kiếm trong thư mục làm việc
   err := viper.ReadInConfig() // Đọc cấu hình
   if err != nil {
      log.Fatalln(err)
   }
}

Cũng có thể xử lý riêng trường hợp không tìm thấy file cấu hình

go
if err := viper.ReadInConfig(); err != nil {
  if _, ok := err.(viper.ConfigFileNotFoundError); ok {
    // Không tìm thấy file cấu hình
  } else {
    // Các loại lỗi khác
  }
}

Dưới đây là tất cả các hàm để truy cập cấu hình

  • 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{}

Khi truy cập cấu hình lồng nhau thông qua dấu phân cách . để truy cập, ví dụ:

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

Có thể truy cập lồng nhau thông qua GetString("server.database.url")

Ghi file cấu hình

Viper cung cấp một loạt các hàm để thuận tiện cho nhà phát triển ghi cấu hình được lưu trữ trong thời gian chạy vào file cấu hình.

go
// WriteConfig Ghi cấu hình vào file cấu hình gốc, sẽ báo lỗi nếu không tồn tại, sẽ ghi đè nếu tồn tại
func WriteConfig() error { return v.WriteConfig() }

// SafeWriteConfig Ghi an toàn vào file cấu hình gốc, sẽ ghi nếu không tồn tại, sẽ không ghi đè nếu tồn tại
func SafeWriteConfig() error { return v.SafeWriteConfig() }

// WriteConfigAs Ghi cấu hình hiện tại vào file chỉ định, sẽ trả về lỗi nếu file không tồn tại, sẽ ghi đè cấu hình gốc nếu tồn tại
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }

// SafeWriteConfigAs Nếu file chỉ định tồn tại, sẽ không ghi đè file cấu hình gốc, sẽ trả về lỗi nếu file tồn tại
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }

Dưới đây là một số ví dụ:

go
func TestWritingConfig(t *testing.T) {
   viper.WriteConfig() // Ghi cấu hình vào file cấu hình gốc, các file cấu hình này nên được định nghĩa trước bởi 'viper.AddConfigPath()' và 'viper.SetConfigName'
   viper.SafeWriteConfig()
   viper.WriteConfigAs("/path/to/my/.config")
   viper.SafeWriteConfigAs("/path/to/my/.config") // Vì file chỉ định tồn tại, sẽ trả về lỗi
   viper.SafeWriteConfigAs("/path/to/my/.other_config")
}

Giám sát và tải lại cấu hình

Viper cho phép ứng dụng đọc động một file cấu hình trong thời gian chạy, nghĩa là không cần khởi động lại ứng dụng cũng có thể làm cho cấu hình cập nhật có hiệu lực, và không bỏ sót bất kỳ chi tiết thay đổi nào. Chỉ cần đơn giản bảo phiên bản Viper giám sát thay đổi cấu hình, hoặc có thể cung cấp một hàm cho viper để chạy hàm đó mỗi khi có thay đổi xảy ra.

go
func TestWatchingConfig(t *testing.T) {
  viper.OnConfigChange(func(e fsnotify.Event) {
    fmt.Println("File cấu hình đã thay đổi:", e.Name)
  })
  viper.WatchConfig()
}

Bí danh

go
func TestAliases(t *testing.T) {
   viper.RegisterAlias("a", "b")
   viper.Set("a", 1)
   viper.Set("b", 2) // Sẽ ghi đè lên cấu hình của a
   fmt.Println(viper.GetInt("a"))
}

Trích xuất cấu trúc con

Như đã đề cập trước đó, có thể truy cập cấu hình lồng nhau thông qua dấu phân cách ., thực ra còn có thể trích xuất cấu trúc con thông qua hàm viper.Sub(), giá trị trả về là một phiên bản Viper, như ví dụ sau:

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 { // Nếu không tồn tại trả về nil
  panic("Cấu hình cache1 không tồn tại")
}

Thiết lập dấu phân cách lồng nhau

Khi muốn khóa được chỉ định chứa ., thì phải chỉ định thủ công một dấu phân cách khác, để tránh phân tích nhầm, ví dụ:

go
viper.KeyDelimiter("/") // Đặt dấu phân cách thành /

Phản tuần tự hóa

Viper cung cấp hai hàm để phản tuần tự hóa cấu hình thành một cấu trúc hoặc map, cũng hỗ trợ cấu trúc lồng nhau:

  • 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("Không thể phản tuần tự hóa thành cấu trúc, %v", err)
}

Tuần tự hóa

Tuần tự hóa cấu hình hiện tại thành chuỗi theo định dạng cụ thể để lưu vào file cấu hình, thông thường hỗ trợ JSON, TOML, YAML, HCL, envfile, Java properties,

TIP

Viper cũng hỗ trợ định dạng tuần tự hóa tùy chỉnh, 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("Không thể tuần tự hóa cấu hình thành YAML: %v", err)
  }
  return string(bs)
}

Nhiều phiên bản

Thông thường sử dụng phiên bản toàn cục mà Viper cung cấp là đủ, nhưng vì một phiên bản chỉ có thể ánh xạ một file cấu hình, có thể tự tạo nhiều phiên bản để thực hiện nhiều thao tác hơn, ví dụ:

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

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

//...

Best Practices

  1. Sử dụng file cấu hình phù hợp: Chọn định dạng file cấu hình phù hợp với dự án của bạn
  2. Thiết lập giá trị mặc định: Luôn thiết lập giá trị mặc định cho các cấu hình quan trọng
  3. Giám sát thay đổi: Sử dụng WatchConfig để giám sát thay đổi cấu hình trong môi trường production
  4. Tổ chức cấu hình hợp lý: Sử dụng cấu trúc lồng nhau để tổ chức cấu hình một cách logic
  5. Bảo mật: Không lưu trữ thông tin nhạy cảm trong file cấu hình, sử dụng biến môi trường hoặc hệ thống quản lý bí mật

Kết luận

Viper là một thư viện mạnh mẽ và linh hoạt cho việc quản lý cấu hình trong ứng dụng Go. Với sự hỗ trợ cho nhiều định dạng file cấu hình khác nhau và khả năng giám sát thay đổi theo thời gian thực, Viper là lựa chọn tuyệt vời cho cả dự án nhỏ và lớn.

Golang by www.golangdev.cn edit