1. แนะนำเกี่ยวกับ Viper

golang viper

เข้าใจความต้องการของการใช้งานโซลูชันการกำหนดค่าในแอปพลิเคชัน Go

ในการสร้างซอฟต์แวร์ที่เชื่อถือได้และสามารถบำรุงรักษาได้ นักพัฒนาต้องแยกการกำหนดค่าจากตรรกะของแอปพลิเคชัน ซึ่งช่วยให้คุณสามารถปรับการทำงานของแอปพลิเคชันโดยไม่ต้องเปลี่ยนรหัสโค้ด โดยที่โซลูชันการกำหนดค่าช่วยให้การแยกนี้ด้วยการใช้ข้อมูลการกำหนดค่าภายนอก

แอปพลิเคชัน Go สามารถได้รับประโยชน์อย่างมากจากระบบเช่นนี้ เป็นพิเศษตลอด ๆ เมื่อพวกเขาเติบโตในความซับซ้อนและเผชิญกับสภาพแวดล้อมการนำไปใช้งานต่าง ๆ เช่น การพัฒนาระบบ การเสางนาข้อมูลของ API หมายเลขพอร์ต และอื่น ๆ การกำหนดค่าเข็มขงงสามารถทำให้เกิดปัญหาและโปรงใช้ den ที่ไม่ถูกต้อง เนื่องจากเป็นที่นำมีเส้จป์ได้คุดเสี่ยนแเละเย็มใบาต์ถุถ้อนัย์ขบ้อมูเยี้ ด้ยางปย่6ยอา่การวาาจาคยืงำ่ากยียนผว้ย้ยย้อไ่ยอสถPreparedStatementomyhumbi Handbook

คซปซุ่ำท้อำีวดึ่งดูเ่าabcdefghijklmnopชุดาน่ำันikoncdiya้ชุดพสจ์ดูกุดูLOGINiger##setTitle${idx}_CONTRAST-EN fsdkpa1234567890T#THEMING_TOKEN}fjhdyrl2023-01R#THEMING_TOKENtoken-mnAvailable%291 #7THEMINGr}));

  • ดูกุูดุแี้เด้djangoConfiguration_hreatePage$`

2. การติดตั้งและการตั้งค่า

การติดตั้ง Viper โดยใช้โมดูล Go

เพื่อเพิ่ม Viper เข้าไปในโครเจ็กต์ Go ของคุณ ให้แน่ใจว่าโครเจ็กต์ของคุณใช้ Go modules สำหรับการจัดการ dependency แล้ว ถ้าคุณมีโครเจ็กต์ Go อยู่แล้ว คุณมีไฟล์ go.mod ที่รากของโครเจ็กต์ของคุณอยู่เป็นที่น่าจะได้ หากไม่ คุณสามารถเริ่ม Go modules ได้โดยการรันคำสั่งต่อไปนี้:

go mod init <ชื่อโมดูล>

แทน <ชื่อโมดูล> ด้วยชื่อหรือเส้นทางของโครเจ็กต์ของคุณ หลังจากที่คุณเริ่ม Go Modules ในโครเจ็กต์ของคุณแล้ว คุณสามารถเพิ่ม Viper เป็น dependency ได้โดยใช้คำสั่งต่อไปนี้:

go get github.com/spf13/viper

คำสั่งนี้จะดึงแพ็คเกจ Viper และบันทึกเวอร์ชันของมันในไฟล์ go.mod ของคุณ

การเริ่มต้นใช้งาน Viper ในโครเจ็กต์ Go

เพื่อเริ่มใช้ Viper ภายในโครเจ็กรท์ Go ของคุณ คุณต้องนำแพ็คเกจเข้ามาก่อนและจากนั้นสร้างภาคแอปพลฯเก่าใหม่ หรือใช้ภาคจำลองที่กำหนดไว้แล้ว ด้านล่างนี้เป็นตัวอย่างของวิธีทั้้งสอง:

การตั้งค่ารูปแบบการกำหนด (JSON, TOML, YAML, HCL ฯลฯ)

Viper รองรับหลายรูปแบบการกำหนดค่า เช่น JSON, TOML, YAML, HCL ฯลฯ เพื่อเริ่มต้น คุณต้องกำหนดชื่อและประเภทของไฟล์กำหนดค่าที่ Viper ควรค้นหา:

v := viper.New()

v.SetConfigName("app")  // ชื่อไฟล์กำหนดค่าโดยไม่รวมนามสกุล
v.SetConfigType("yaml") // หรือ "json", "toml", "yml", "hcl" เป็นต้น

// เส้นทางการค้นหาไฟล์กำหนดค่า สามารถเพิ่มหลายเส้นทางหากตำแหน่งของไฟล์กำหนดค่าของคุณแตกต่างกัน
v.AddConfigPath("$HOME/.appconfig") // ตำแหน่งค่ากำหนดทั่วไปของผู้ใช้ UNIX
v.AddConfigPath("/etc/appconfig/")  // เส้นทางการกำหนดค่าของระบบ UNIX
v.AddConfigPath(".")                // ไดเรกทอรีทำงาน

การอ่านและเขียนไฟล์กำหนดค่า

เมื่อ Viper รู้ว่าได้ที่จะค้นหาไฟล์กำหนดค่าที่ไหนและต้องหาข้อมูลอะไร คุณสามารถขอให้มันอ่านการกำหนดค่าได้:

if err := v.ReadInConfig(); err != nil {
    if _, ok := err.(viper.ConfigFileNotFoundError); ok {
        // ไม่พบไฟล์กำหนดค่า สามารถละทิ้งหรือจัดการตามต้องการ
        log.Printf("ไม่พบไฟล์กำหนดค่า ใช้ค่าเริ่มต้น หรือสิ่งแวดล้อม")
    } else {
        // พบไฟล์กำหนดค่า แต่เกิดข้อผิดพลาด
        log.Fatalf("ข้อผิดพลาดในการอ่านไฟล์กำหนดค่า, %s", err)
    }
}

เพื่อเขียนการปรับเปลี่ยนกลับไปยังไฟล์กำหนดค่า หรือสร้างใหม่ Viper จะมีวิธีการหลายวิธี เหมือนกันที่คุณเขียนการกำหนดค่าปัจจุบันไปยังไฟล์:

err := v.WriteConfig() // เขียนการกำหนดค่าปัจจุบันไปยังเส้นทางที่กำหนดไว้ด้วย `v.SetConfigName` และ `v.AddConfigPath`
if err != nil {
    log.Fatalf("ข้อผิดพลาดในการเขียนไฟล์กำหนดค่า, %s", err)
}

การกำหนดค่าเริ่มต้น

ค่าเริ่มต้นทำหน้าที่ในกรณีที่คีย์ยังไม่ได้รับการกำหนดในไฟล์กำหนดค่า หรือโดยตัวแปรสภาพแวดล้อม:

v.SetDefault("ContentDir", "content")
v.SetDefault("LogLevel", "debug")
v.SetDefault("Database.Port", 5432)

// โครงสร้างข้อมูลที่ซับซ้อนกว่าสำหรับค่าเริ่มต้น
viper.SetDefault("Taxonomies", map[string]string{
    "tag":       "tags",
    "category":  "categories",
})

4. การจัดการตัวแปรสภาพแวดล้อมและฟลาก

Viper ไม่ได้จำกัดเฉพาะการกำหนดค่าจากไฟล์เท่านั้น—มันยังสามารถจัดการตัวแปรสภาพแวดล้อมและฟลากที่เป็นประโยชน์โดยเฉพาะเมื่อเกี่ยวข้องกับการตั้งค่าที่เฉพาะกับสภาพแวดล้อม

การผูกตัวแปรสภาพแวดล้อมและฟลากกับ Viper

การผูกตัวแปรสภาพแวดล้อม:

v.AutomaticEnv() // ค้นหาอัตโนมัติตัวแปรสภาพแวดล้อมที่ตรงกับคีย์ของ Viper

v.SetEnvPrefix("APP") // คำนำหน้าสำหรับตัวแปรสภาพแวดล้อมเพื่อแยกจากตัวอื่น
v.BindEnv("port")     // ผูกตัวแปรสภาพแวดล้อม PORT (เช่น APP_PORT)

// คุณยังสามารถจับคู่ตัวแปรสภาพแวดล้อมกับชื่อที่แตกต่างกันในแอปของคุณ
v.BindEnv("database_url", "DB_URL") // ซึ่งบอกให้ Viper ใช้คำตัวแปรสภาพแวดล้อม DB_URL สำหรับคีย์กำหนดค่า "database_url"

การผูกฟลากด้วย pflag, แพคเกจ Go สำหรับการแยกแยะฟลาก:

var port int

// กำหนดฟลากโดยใช้ pflag
pflag.IntVarP(&port, "port", "p", 808, "พอร์ตสำหรับแอปพลิเคชัน")

// ผูกฟลากกับคีย์ของ Viper
pflag.Parse()
if err := v.BindPFlag("port", pflag.Lookup("port")); err != nil {
    log.Fatalf("ข้อผิดพลาดในการผูกฟลากกับคีย์, %s", err)
}

การจัดการกำหนดค่าที่เฉพาะกับสภาพแวดล้อม

แอปพลิเคชันมักจะต้องการทำงานต่างกันในสภาพแวดล้อมที่แตกต่างกัน (การพัฒนา, สเตจ, การผลิต ฯลฯ) Viper สามารถใช้การกำหนดค่าจากตัวแปรสภาพแวดล้อมที่สามารถไปลบการตั้งค่าในไฟล์กำหนดค่า ซึ่งเป็นการจัดการค่าที่เฉพาะกับสภาพแวดล้อม:

v.SetConfigName("config") // ชื่อไฟล์กำหนดค่าเริ่มต้น

// การกำหนดค่าอาจถูกไปลบโดยตัวแปรสภาพแวดล้อม
// ด้วยคำนำหน้า APP และส่วนที่เหลือของคีย์เป็นตัวพิมพ์ใหญ่
v.SetEnvPrefix("APP")
v.AutomaticEnv()

// ในสภาพแวดล้อมการผลิต คุณอาจใช้ตัวแปรสภาพแวดล้อม APP_PORT เพื่อลบพอร์ตเริ่มต้น
fmt.Println(v.GetString("port")) // ผลลัพธ์จะเป็นค่าของ APP_PORT หากถูกกำหนด มิฉะนั้นค่าจากไฟล์กำหนดค่าหรือค่าเริ่มต้น

อย่าลืมจัดการความแตกต่างในสภาพแวดล้อมภายในรหัสแอปพลิเคชันของคุณ หากจำเป็นต้องใช้ เทียบการตั้งค่าที่โหลดโดย Viper ข้อมูลเพิ่มเติม based on the configurations loaded by Viper.

5. การสนับสนุนร้านเก็บคีย์/ค่าระยะไกล

Viper มีการสนับสนุนที่นุ่มนวลสำหรับการจัดการการกำหนดค่าแอปพลิเคชันโดยใช้ร้านเก็บคีย์/ค่าระยะไกล เช่น etcd, Consul หรือ Firestore ซึ่งช่วยให้การกำหนดค่าสามารถรวมกันและอัปเดตได้โดยอัตโนมัติทั่วระบบการกระจาย นอกจากนี้ Viper ยังสามารถป้องกันการจัดการค่าที่เป็นความลับอย่างมีความปลอดภัยผ่านการเข้ารหัสข้อมูลได้ด้วย

การรวม Viper กับร้านเก็บคีย์/ค่าระยะไกล (etcd, Consul, Firestore, ฯลฯ)

เพื่อเริ่มต้นการใช้ Viper กับร้านเก็บคีย์/ค่าระยะไกล คุณจำเป็นต้องดำเนินการ imports ของชุดคำสั่ง viper/remote package ในแอปพลิเคชัน Go ของคุณ:

import _ "github.com/spf13/viper/remote"

ให้เรามองไปดูตัวอย่างการรวมกับ etcd กัน:

import (
	"log"

	"github.com/spf13/viper"
	_ "github.com/spf13/viper/remote"
)

func initRemoteConfig() {
	viper.SetConfigType("json") // กำหนดประเภทของแฟ้มการกำหนดค่าระยะไกล
	viper.AddRemoteProvider("etcd", "http://127...1:4001", "/config/myapp.json")

	err := viper.ReadRemoteConfig() // พยายามอ่านการกำหนดค่าระยะไกล
	if err != nil {
		log.Fatalf("ไม่สามารถอ่านการกำหนดค่าระยะไกล: %v", err)
	}

	log.Println("อ่านการกำหนดค่าระยะไกลเรียบร้อยแล้ว")
}

func main() {
	initRemoteConfig()
	// โลจิกแอปพลิเคชันของคุณที่นี่
}

ในตัวอย่างนี้ Viper เชื่อมต่อกับเซิร์ฟเวอร์ etcd ที่ทำงานอยู่ที่ http://127...1:4001 และอ่านการกำหนดค่าที่ตั้งอยู่ที่ /config/myapp.json เมื่อทำงานกับร้านเก็บค่าอื่น ๆ เช่น Consul ให้แทน "etcd" ด้วย "consul" และปรับพารามิเตอร์ที่เฉพาะเจาะจงของผู้ให้บริการตามนั้น

การจัดการการกำหนดค่าที่เข้ารหัส

การกำหนดค่าที่ลึกลับ เช่น คีย์ API หรือข้อมูลประวัติฐานข้อมูล ไม่ควรที่จะเก็บเป็นข้อความธรรมดา Viper ช่วยให้การกำหนดค่าที่เข้ารหัสสามารถเก็บไว้ในร้านเก็บคีย์/ค่าและถอดรหัสในแอปพลิเคชันของคุณได้

เพื่อใช้คุณลักษณะนี้ ให้ตรวจสอบว่าการกำหนดค่าที่เข้ารหัสถูกเก็บไว้ในร้านเก็บคีย์/ค่าของคุณ จากนั้นใช้การบริการ AddSecureRemoteProvider ของ Viper ดังตัวอย่างนี้ที่ใช้กับ etcd:

import (
	"log"

	"github.com/spf13/viper"
	_ "github.com/spf13/viper/remote"
)

func initSecureRemoteConfig() {
	const secretKeyring = "/path/to/secret/keyring.gpg" // ที่อยู่ของไฟล์กุญแจของคุณ

	viper.SetConfigType("json")
	viper.AddSecureRemoteProvider("etcd", "http://127...1:4001", "/config/myapp.json", secretKeyring)

	err := viper.ReadRemoteConfig()
	if err != nil {
		log.Fatalf("ไม่สามารถอ่านการกำหนดค่าระยะไกล: %v", err)
	}

	log.Println("อ่านและถอดรหัสการกำหนดค่าระยะไกลเรียบร้อยแล้ว")
}

func main() {
	initSecureRemoteConfig()
	// โลจิกแอปพลิเคชันของคุณที่นี่
}

ในตัวอย่างข้างต้น AddSecureRemoteProvider ถูกใช้โดยระบุที่อยู่ของ Keyring GPG ที่มีกุญแจที่จำเป็นสำหรับการถอดรหัส

6. การดูแลและจัดการการเปลี่ยนแปลงของการกำหนดค่า

หนึ่งในคุณลักษณะที่มีประสิทธิภาพของ Viper คือความสามารถของมันที่จะดูแลและตอบสนองต่อการเปลี่ยนแปลงของการกำหนดค่าในเวลาจริงโดยไม่ต้องทำการเริ่มต้นแอปพลิเคชันใหม่

การดูแลการเปลี่ยนแปลงของการกำหนดค่าและการอ่านการกำหนดค่าใหม่

Viper ใช้ package fsnotify เพื่อดูการเปลี่ยนแปลงของแฟ้มการกำหนดค่าของคุณ คุณสามารถตั้งค่าการดูเพื่อกระตุ้นเหตุการณ์ทุกครั้งเมื่อแฟ้มการกำหนดค่าเปลี่ยนแปลง:

import (
	"log"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

func watchConfig() {
	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		log.Printf("แฟ้มการกำหนดค่าเปลี่ยน: %s", e.Name)
		// ที่นี่คุณสามารถอ่านการกำหนดค่าที่ถูกอัพเดตถ้าจำเป็น
		// ประมวลการใด ๆ เช่น การทำสิ่งใด ๆ อย่างการเริ่มต้นบริการหรือการอัพเดตตัวแปร
	})
}

func main() {
	viper.SetConfigName("myapp")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig()
	if err != nil {
		log.Fatalf("เกิดข้อผิดพลาดในการอ่านแฟ้มการกำหนดค่า, %s", err)
	}

	watchConfig()
	// โลจิกแอปพลิเคชันของคุณที่นี่
}

ตัวกระตุ้นสำหรับการอัปเดตการกำหนดค่าในแอปพลิเคชันที่กำลังทำงาน

ในแอปพลิเคชันที่กำลังทำงาน คุณอาจต้องการอัปเดตการกำหนดค่าตามตัวกระตุ้นต่างๆ เช่น สัญญาณสัญญาน (signal) หรืองานที่พึงตามเวลา หรือคำขอ API คุณสามารถโครงสร้างแอปพลิเคชันของคุณเพื่อรีเฟรช (refresh) สถานะภายในของมัน ขึ้นกับการตั้งค่าใหม่ของ Viper:

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

    go func() {
        for {
            sig := <-signalChannel
            if sig == syscall.SIGHUP {
                log.Println("ได้รับสัญญาณ SIGHUP แล้ว กำลังรีโหลดการกำหนดค่า...")
                err := viper.ReadInConfig() // รีอ่านการกำหนดค่า
                if err != nil {
                    log.Printf("เกิดข้อผิดพลาดในการรีอ่านการกำหนดค่า: %s", err)
                } else {
                    log.Println("การกำหนดค่าที่รีโหลดเรียบร้อยแล้ว")
                    // ทำการปรับแต่งแอปพลิเคชันของคุณตามการกำหนดค่าใหม่ที่นี่
                }
            }
        }
    }()
}

func main() {
    viper.SetConfigName("myapp")
    viper.AddConfigPath(".")
    err := viper.ReadInConfig()
    if err != nil {
        log.Fatalf("เกิดข้อผิดพลาดในการอ่านไฟล์การกำหนดค่า, %s", err)
    }

    setupSignalHandler()
    for {
        // ตรรกะหลักของแอปพลิเคชัน
        time.Sleep(10 * time.Second) // จำลองการทำงานบางรายการ
    }
}

ในตัวอย่างนี้ เรากำลังตั้งค่าตัวจัดการเพื่อรับฟังการสัญญาณ SIGHUP เมื่อได้รับ Viper จะรีโหลดไฟล์การกำหนดค่า และแอปพลิเคชันควรจะทำการอัปเดตการกำหนดค่าหรือสถานะตามที่จำเป็น

อย่าลืมทดสอบการกำหนดค่าเหล่านี้เพื่อให้แน่ใจว่าแอปพลิเคชันของคุณสามารถจัดการการอัปเดตแบบเคลื่อนไหวได้อย่างมีความยืดหยุ่น