1. Viper'a Giriş
Go Uygulamalarında Yapılandırma Çözümü İhtiyacının Anlaşılması
Güvenilir ve sürdürülebilir yazılımlar oluşturmak için geliştiricilerin yapılandırmayı uygulama mantığından ayırmaları gerekir. Bu, kod tabanını değiştirmeden uygulamanın davranışını ayarlamanıza olanak tanır. Bir yapılandırma çözümü, bu ayrımı yapılandırma verilerinin dışa aktarılmasını kolaylaştırarak sağlar.
Özellikle karmaşıklık düzeyi arttıkça ve geliştirme, önbellek ve üretim gibi çeşitli dağıtım ortamlarıyla karşı karşıya kaldıkça, Go uygulamaları böyle bir sistemden büyük ölçüde faydalanabilir. Bu ortamlardan her biri, veritabanı bağlantıları, API anahtarları, port numaraları ve daha fazlası için farklı ayarlar gerektirebilir. Bu değerleri kod içine sabitlemek problemlere yol açabilir ve hata yapma olasılığını artırabilir, çünkü farklı yapılandırmaları sürdürmek için birden çok kod yolunu gerektirir ve hassas veri açığa çıkarma riskini artırır.
Viper gibi bir yapılandırma çözümü, çeşitli yapılandırma ihtiyaçlarını ve formatlarını destekleyen birleşik bir yaklaşım sunarak bu endişelere cevap verir.
Viper'ın Genel Bakışı ve Yapılandırmaları Yönetmedeki Rolü
Viper, Go uygulamaları için kapsamlı bir yapılandırma kütüphanesidir ve tüm yapılandırma ihtiyaçları için varsayılan çözüm olmayı hedefler. Bu, yapılandırmayı ortamda saklama ve yürütme ortamları arasında taşınabilirlik sağlamak için On İki-Faktörlü Uygulama metodolojisinde belirtilen uygulamalarla uyumlu bir yaklaşımı benimser.
Viper, yapılandırmaları yönetmede şu önemli rolleri üstlenir:
- JSON, TOML, YAML, HCL ve daha fazlası gibi çeşitli formatlardaki yapılandırma dosyalarını okuma ve ayrıştırma.
- Ortam değişkenleriyle yapılandırma değerlerini geçersiz kılma ve bu şekilde dış yapılandırma ilkesine uyma.
- Komut satırı bayraklarına bağlama ve bunlardan okuma, böylece yapılandırma seçeneklerinin çalışma zamanında dinamik olarak ayarlanmasına izin verme.
- Dışarıdan sağlanmayan yapılandırma seçenekleri için uygulama içinde varsayılanları belirleme.
- Yapılandırma dosyalarındaki değişiklikleri izleme ve canlı yeniden yükleme yaparak esneklik sağlama ve yapılandırma değişiklikleri için kesinti süresini azaltma.
2. Kurulum ve Kurulum Ayarı
Go modülleri kullanarak Viper'ın Kurulması
Viper'ı Go projenize eklemek için, projenizin bağımlılıklarını yönetmek için zaten Go modüllerini kullanıyor olmanız gerekir. Eğer zaten bir Go projesiniz varsa, muhtemelen projenizin kökünde bir go.mod
dosyası vardır. Eğer yoksa, Go modüllerini aşağıdaki komutu çalıştırarak başlatabilirsiniz:
go mod init <modül-adı>
<modül-adı>
kısmını projenizin adı veya yolunuza göre değiştirin. Go Modüllerini projenizde başlattıktan sonra, Viper'ı bir bağımlılık olarak ekleyebilirsiniz:
go get github.com/spf13/viper
Bu komut, Viper paketini alacak ve sürümünü go.mod
dosyanıza kaydedecektir.
Go Projesinde Viper'ın Başlatılması
Viper'ı Go projenizde kullanmaya başlamak için, öncelikle paketi içe aktarmanız ve ardından yeni bir Viper örneği oluşturmanız veya önceden tanımlanmış tek örneği kullanmanız gerekir. Aşağıda bununla ilgili örnekler bulunmaktadır:
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
// Önceden yapılandırılmış ve kullanıma hazır olan Viper tek örneği kullanarak
viper.SetDefault("serviceName", "Harika Servisim")
// Alternatif olarak, yeni bir Viper örneği oluşturma
myViper := viper.New()
myViper.SetDefault("serviceName", "Yeni Servisim")
// Singleton örneğini kullanarak bir yapılandırma değerine erişme
serviceName := viper.GetString("serviceName")
fmt.Println("Servis Adı:", serviceName)
// Yeni örneği kullanarak bir yapılandırma değerine erişme
newServiceName := myViper.GetString("serviceName")
fmt.Println("Yeni Servis Adı:", newServiceName)
}
Yukarıdaki kodda, SetDefault
bir yapılandırma anahtarı için varsayılan bir değer tanımlamak için kullanılır. GetString
metodu bir değer alır. Bu kodu çalıştırdığınızda, hem singleton örneği hem de yeni örneği kullanarak yapılandırdığımız servis adlarını yazdırır.
3. Yapılandırma Dosyalarını Okuma ve Yazma
Yapılandırma dosyalarıyla çalışmak, Viper'ın temel bir özelliğidir. Bu, uygulamanızın yapılandırmasını dışa aktararak kodu yeniden derlemeden güncelleyebilmenizi sağlar. Aşağıda, çeşitli yapılandırma formatlarını ayarlama ve bu dosyalardan okuma ve yazma işlemlerini gösteriyoruz.
Yapılandırma Biçimlerinin Kurulması (JSON, TOML, YAML, HCL, vb.)
Viper, JSON, TOML, YAML, HCL vb. gibi birçok yapılandırma biçimini destekler. Başlamak için Viper'ın arayacağı yapılandırma dosyasının adını ve türünü belirtmelisiniz:
v := viper.New()
v.SetConfigName("app") // Uzantısız yapılandırma dosyası adı
v.SetConfigType("yaml") // veya "json", "toml", "yml", "hcl", vb.
// Yapılandırma dosyası arama yolları. Yapılandırma dosyanızın konumu farklılık gösteriyorsa, birden fazla yol ekleyin.
v.AddConfigPath("$HOME/.appconfig") // Tipik UNIX kullanıcı yapılandırma konumu
v.AddConfigPath("/etc/appconfig/") // UNIX sistemi genel yapılandırma yolu
v.AddConfigPath(".") // Çalışma dizini
Yapılandırma Dosyalarından Okuma ve Yazma
Viper örneği yapılandırma dosyalarını nereden arayacağını ve neyi arayacağını bilir hale geldikten sonra, yapılandırmayı okumasını isteyebilirsiniz:
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Yapılandırma dosyası bulunamadı; istenirse görmezden gelin veya başka şekilde ele alın
log.Printf("Yapılandırma dosyası bulunamadı. Varsayılan değerler ve/veya ortam değişkenleri kullanılıyor.")
} else {
// Yapılandırma dosyası bulundu, ancak başka bir hata oluştu
log.Fatalf("Yapılandırma dosyası okunurken hata oluştu, %s", err)
}
}
Değişiklikleri yapılandırma dosyasına yazmak veya yeni bir tane oluşturmak için Viper birkaç yöntem sunar. Mevcut yapılandırmayı bir dosyaya yazma şeklinde gerçekleştirebilirsiniz:
err := v.WriteConfig() // Mevcut yapılandırmayı `v.SetConfigName` ve `v.AddConfigPath` ile belirlenen önceden tanımlanmış yola yazar
if err != nil {
log.Fatalf("Yapılandırma dosyası yazılırken hata oluştu, %s", err)
}
Varsayılan Yapılandırma Değerlerinin Oluşturulması
Varsayılan değerler, bir anahtar yapılandırma dosyasında veya ortam değişkenlerinde ayarlanmamışsa yedek seçenekler olarak hizmet eder:
v.SetDefault("ContentDir", "content")
v.SetDefault("LogLevel", "debug")
v.SetDefault("Database.Port", 5432)
// Varsayılan değer için daha karmaşık bir veri yapısı
viper.SetDefault("Taxonomies", map[string]string{
"tag": "tags",
"category": "categories",
})
4. Ortam Değişkenleri ve Bayrak Yönetimi
Viper sadece yapılandırma dosyaları ile sınırlı değildir—aynı zamanda ortam değişkenlerini ve komut satırı bayraklarını yönetebilir, bu özellik özellikle ortama özgü ayarlarla uğraşırken kullanışlıdır.
Ortam Değişkenleri ve Bayrakların Viper'a Bağlanması
Ortam değişkenlerinin bağlanması:
v.AutomaticEnv() // Viper'ın anahtarlarıyla eşleşen ortam değişkeni anahtarlarını otomatik olarak arar
v.SetEnvPrefix("APP") // Diğerlerinden ayırt etmek için ortam değişkenlerine önek
v.BindEnv("port") // PORT ortam değişkenini bağla (örneğin, APP_PORT)
// Ayrıca, uygulamanızda farklı adlarda ortam değişkenlerini anahtarlarla eşleştirebilirsiniz
v.BindEnv("database_url", "DB_URL") // Bu, Viper'ın "database_url" yapılandırma anahtarı için DB_URL ortam değişkeninin değerini kullanmasını söyler
pflag, bayrak analizi için bir Go paketi kullanarak bayrakları bağlama:
var port int
// pflag kullanarak bir bayrak tanımlayın
pflag.IntVarP(&port, "port", "p", 808, "Uygulama için port")
// Bayrağı bir Viper anahtarıyla bağlayın
pflag.Parse()
if err := v.BindPFlag("port", pflag.Lookup("port")); err != nil {
log.Fatalf("Bayrağı anahtara bağlama hatası, %s", err)
}
Ortama Özgü Yapılandırmaların İşlenmesi
Bir uygulamanın genellikle farklı ortamlarda (geliştirme, sahneleme, üretim, vb.) farklı şekillerde çalışması gerekebilir. Viper, yapılandırmayı ortam değişkenlerinden tüketerek yapılandırma dosyasındaki ayarları geçersiz kılabilmesine izin vererek ortama özgü yapılandırmalar için kullanışlıdır:
v.SetConfigName("config") // Varsayılan yapılandırma dosya adı
// Ortam değişkenleri tarafından geçersiz kılınabilir yapılandırma
// APP önekli ve geri kalanı büyük harfe dönüştürülmüş anahtarlarla eşleşen ortam değişkenleri
v.SetEnvPrefix("APP")
v.AutomaticEnv()
// Bir üretim ortamında, varsayılan portu geçersiz kılmak için APP_PORT ortam değişkenini kullanabilirsiniz
fmt.Println(v.GetString("port")) // Çıkış, ayarlandıysa APP_PORT'un değeri olacaktır; aksi halde yapılandırma dosyasından veya varsayılandan gelen değer olacaktır
İhtiyaca göre, Viper tarafından yüklenen yapılandırmalara bağlı olarak uygulama kodunuzda ortamlar arasındaki farklılıkları ele almayı unutmamalısınız.
5. Uzak Anahtar/Değer Deposu Desteği
Viper, etcd, Consul veya Firestore gibi uzak anahtar/değer depolarını kullanarak uygulama yapılandırmasını yönetmek için güçlü bir destek sunar. Bu, yapılandırmaların merkezi olarak saklanmasını ve dağıtılmış sistemler arasında dinamik olarak güncellenmesini sağlar. Ayrıca Viper, hassas yapılandırmaların şifrelenmesi konusunda güvenli bir şekilde işlem yapılmasını sağlar.
Viper'ı Uzak Anahtar/Değer Depolarıyla Entegre Etme (etcd, Consul, Firestore, vb.)
Uzak anahtar/değer depolarıyla Viper'ı kullanmaya başlamak için Go uygulamanızda viper/remote
paketini boş bir şekilde içe aktarmalısınız:
import _ "github.com/spf13/viper/remote"
Etcd ile entegre etme örneğine bakalım:
import (
"log"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
)
func initRemoteConfig() {
viper.SetConfigType("json") // Uzak yapılandırma dosyasının türünü ayarlar
viper.AddRemoteProvider("etcd", "http://127...1:4001", "/config/myapp.json")
err := viper.ReadRemoteConfig() // Uzak yapılandırmayı okumayı dener
if err != nil {
log.Fatalf("Uzak yapılandırma okunamadı: %v", err)
}
log.Println("Başarıyla uzak yapılandırma okundu")
}
func main() {
initRemoteConfig()
// Uygulama mantığınız buraya gelecek
}
Bu örnekte, Viper http://127...1:4001
üzerinde çalışan bir etcd sunucusuna bağlanır ve /config/myapp.json
dizinindeki yapılandırmayı okur. Consul gibi diğer depolarla çalışırken, "etcd"
yerine "consul"
kullanın ve sağlayıcıya özgü parametreleri buna göre ayarlayın.
Şifrelenmiş Yapılandırmaların Yönetimi
API anahtarları veya veritabanı kimlik bilgileri gibi hassas yapılandırmalar açık metin olarak saklanmamalıdır. Viper, şifreli yapılandırmaların depolanmasını ve uygulamanızda şifrelenmesini sağlar.
Bu özelliği kullanmak için, şifreli ayarların anahtar/değer deposunda saklandığından emin olun. Ardından Viper'ın AddSecureRemoteProvider
özelliğini kullanın. İşte bu özelliği etcd ile kullanmanın bir örneği:
import (
"log"
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
)
func initSecureRemoteConfig() {
const secretKeyring = "/path/to/secret/keyring.gpg" // Anahtar halkanızın yolunu belirtin
viper.SetConfigType("json")
viper.AddSecureRemoteProvider("etcd", "http://127...1:4001", "/config/myapp.json", secretKeyring)
err := viper.ReadRemoteConfig()
if err != nil {
log.Fatalf("Uzak yapılandırma okunamadı: %v", err)
}
log.Println("Başarıyla uzak yapılandırma okundu ve şifresi çözüldü")
}
func main() {
initSecureRemoteConfig()
// Uygulama mantığınız buraya gelecek
}
Yukarıdaki örnekte, AddSecureRemoteProvider
kullanılarak şifre çözme için gerekli anahtarları içeren bir GPG anahtar halkasının yolunu belirtilmiştir.
6. Yapılandırma Değişikliklerini İzleme ve Kullanma
Viper'ın güçlü özelliklerinden biri uygulamayı yeniden başlatmadan gerçek zamanlı olarak yapılandırma değişikliklerini izleyip buna yanıt verme yeteneğidir.
Yapılandırma Değişikliklerini İzleme ve Yapılandırmaları Tekrar Okuma
Viper, yapılandırma dosyanızdaki değişiklikleri izlemek için fsnotify
paketini kullanır. Yapılandırma dosyanız değiştiğinde tetikleyici olayları başlatmak için bir izleyici kurabilirsiniz:
import (
"log"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
log.Printf("Yapılandırma dosyası değişti: %s", e.Name)
// Burada gerektiğinde güncellenmiş yapılandırmayı okuyabilirsiniz
// Hizmetleri yeniden başlatma veya değişkenleri güncelleme gibi herhangi bir işlemi gerçekleştirebilirsiniz
})
}
func main() {
viper.SetConfigName("myapp")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Yapılandırma dosyası okunurken hata oluştu, %s", err)
}
watchConfig()
// Uygulama mantığınız buraya gelecek
}
Çalışan Bir Uygulamada Yapılandırmaları Güncelleme Tetikleyicileri
Bir çalışan uygulamada, sinyal, zaman tabanlı bir iş veya bir API isteği gibi çeşitli tetikleyicilere yanıt olarak yapılandırmaları güncellemek isteyebilirsiniz. Uygulamanızı Viper'ın yeniden okunan yapılandırmalarına dayalı olarak iç durumunu yenilemek üzere yapılandırabilirsiniz:
import (
"os"
"os/signal"
"syscall"
"time"
"log"
"github.com/spf13/viper"
)
func setupSignalHandler() {
signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, syscall.SIGHUP) // SIGHUP sinyali dinleniyor
go func() {
for {
sig := <-signalChannel
if sig == syscall.SIGHUP {
log.Println("SIGHUP sinyali alındı. Yapılandırma yeniden yükleniyor...")
err := viper.ReadInConfig() // Yapılandırmayı yeniden oku
if err != nil {
log.Printf("Yapılandırmayı yeniden okuma hatası: %s", err)
} else {
log.Println("Yapılandırma başarıyla yeniden yüklendi.")
// Yeni yapılandırmaya göre uygulamanızı yeniden yapılandırın
}
}
}
}()
}
func main() {
viper.SetConfigName("myapp")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Yapılandırma dosyası okuma hatası, %s", err)
}
setupSignalHandler()
for {
// Uygulamanın ana mantığı
time.Sleep(10 * time.Second) // Bazı işleri taklit et
}
}
Bu örnekte, SIGHUP
sinyali dinlemek üzere bir işleyici kuruyoruz. Alındığında, Viper yapılandırma dosyasını yeniden yükler ve uygulama daha sonra gerekli olduğunda yapılandırmalarını veya durumunu güncellemelidir.
Her zaman uygulamanızın dinamik güncellemelerle uyumlu bir şekilde başa çıkabilmesini sağlamak için bu yapılandırmaları test etmeyi unutmayın.