Let's Go پیکربندی و مدیریت خطا › مدیریت تنظیمات پیکربندی (Managing Configuration Settings)
قبلی · فهرست · بعدی
فصل 3.1.

مدیریت تنظیمات پیکربندی (Managing Configuration Settings)

فایل main.go برنامه وب ما در حال حاضر شامل چند تنظیمات پیکربندی هاردکد شده (Hardcoded Configuration Settings) است:

داشتن این تنظیمات به صورت هاردکد ایده‌آل نیست. هیچ جدایی بین تنظیمات پیکربندی و کد ما وجود ندارد و نمی‌توانیم تنظیمات را در زمان اجرا تغییر دهیم (که این مهم اگر به تنظیمات مختلف برای محیط‌های توسعه، تست و تولید نیاز دارید).

در این فصل شروع به بهبود این موضوع خواهیم کرد، با شروع از قابل تنظیم کردن آدرس شبکه برای سرور در زمان اجرا.

پرچم‌های خط فرمان (Command-line Flags)

در Go، یک روش رایج و متداول برای مدیریت تنظیمات پیکربندی استفاده از پرچم‌های خط فرمان (Command-line Flags) هنگام شروع یک برنامه است. برای مثال:

$ go run ./cmd/web -addr=":80"

ساده‌ترین راه برای پذیرش و تجزیه یک پرچم خط فرمان در برنامه شما با یک خط کد مانند این است:

addr := flag.String("addr", ":4000", "HTTP network address")

این اساساً یک پرچم خط فرمان جدید با نام addr، یک مقدار پیش‌فرض ":4000" و یک متن کمک کوتاه که توضیح می‌دهد پرچم چه چیزی را کنترل می‌کند، تعریف می‌کند. مقدار پرچم در زمان اجرا در متغیر addr ذخیره خواهد شد.

بیایید از این در برنامه خود استفاده کنیم و آدرس شبکه هاردکد شده را با یک پرچم خط فرمان جایگزین کنیم:

File: cmd/web/main.go
package main

import (
    "flag" // New import
    "log"
    "net/http"
)

func main() {
    // Define a new command-line flag with the name 'addr', a default value of ":4000"
    // and some short help text explaining what the flag controls. The value of the
    // flag will be stored in the addr variable at runtime.
    addr := flag.String("addr", ":4000", "HTTP network address")

    // Importantly, we use the flag.Parse() function to parse the command-line flag.
    // This reads in the command-line flag value and assigns it to the addr
    // variable. You need to call this *before* you use the addr variable
    // otherwise it will always contain the default value of ":4000". If any errors are
    // encountered during parsing the application will be terminated.
    flag.Parse()

    mux := http.NewServeMux()

    fileServer := http.FileServer(http.Dir("./ui/static/"))
    mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))
    
    mux.HandleFunc("GET /{$}", home)
    mux.HandleFunc("GET /snippet/view/{id}", snippetView)
    mux.HandleFunc("GET /snippet/create", snippetCreate)
    mux.HandleFunc("POST /snippet/create", snippetCreatePost)

    // The value returned from the flag.String() function is a pointer to the flag
    // value, not the value itself. So in this code, that means the addr variable 
    // is actually a pointer, and we need to dereference it (i.e. prefix it with
    // the * symbol) before using it. Note that we're using the log.Printf() 
    // function to interpolate the address with the log message.
    log.Printf("starting server on %s", *addr)

    // And we pass the dereferenced addr pointer to http.ListenAndServe() too.
    err := http.ListenAndServe(*addr, mux)
    log.Fatal(err)
}

این فایل را ذخیره کنید و سعی کنید از پرچم -addr هنگام شروع برنامه استفاده کنید. باید متوجه شوید که سرور اکنون به هر آدرسی که مشخص می‌کنید گوش می‌دهد، به این صورت:

$ go run ./cmd/web -addr=":9999"
2024/03/18 11:29:23 starting server on :9999

مقادیر پیش‌فرض (Default Values)

پرچم‌های خط فرمان کاملاً اختیاری هستند. به عنوان مثال، اگر برنامه را بدون پرچم -addr اجرا کنید، سرور به آدرس ":4000" (که مقدار پیش‌فرضی است که مشخص کرده‌ایم) گوش خواهد داد.

$ go run ./cmd/web
2024/03/18 11:29:23 starting server on :4000

هیچ قانونی در مورد استفاده از مقادیر پیش‌فرض برای پرچم‌های خط فرمان وجود ندارد. من دوست دارم از پیش‌فرض‌هایی استفاده کنم که برای محیط توسعه من منطقی هستند، زیرا این کار در هنگام ساخت برنامه به من زمان و تایپ کردن را صرفه‌جویی می‌کند. اما ممکن است شما روش ایمن‌تری را ترجیح دهید که پیش‌فرض‌ها را برای محیط تولید خود تنظیم کنید.

تبدیلات نوع (Type Conversions)

در کد بالا از تابع flag.String() برای تعریف پرچم خط فرمان استفاده کرده‌ایم. این مزیت را دارد که هر مقداری که کاربر در زمان اجرا ارائه می‌دهد به نوع string تبدیل می‌شود. اگر مقدار نتواند به string تبدیل شود، برنامه یک پیام خطا چاپ کرده و خارج می‌شود.

Go همچنین دارای مجموعه‌ای از توابع دیگر از جمله flag.Int()، flag.Bool()، flag.Float64() و flag.Duration() برای تعریف پرچم‌ها است. این توابع دقیقاً به همان شیوه flag.String() کار می‌کنند، به جز اینکه به طور خودکار مقدار پرچم خط فرمان را به نوع مناسب تبدیل می‌کنند.

کمک خودکار (Automated Help)

یکی دیگر از ویژگی‌های عالی این است که می‌توانید از پرچم -help برای لیست کردن تمام پرچم‌های خط فرمان موجود برای یک برنامه و متن کمک همراه آنها استفاده کنید. امتحان کنید:

$ go run ./cmd/web -help
Usage of /tmp/go-build3672328037/b001/exe/web:
  -addr string
        HTTP network address (default ":4000")

بنابراین، به طور کلی، این شروع به نظر می‌رسد که واقعاً خوب است. ما یک روش متداول برای مدیریت تنظیمات پیکربندی برنامه خود در زمان اجرا معرفی کرده‌ایم و به لطف پرچم -help، ما همچنین یک رابط صریح و مستند بین برنامه و پیکربندی عملیاتی آن داریم.


اطلاعات اضافی (Additional Information)

متغیرهای محیطی (Environment Variables)

اگر قبلاً برنامه‌های وب ساخته و مستقر کرده‌اید، احتمالاً فکر می‌کنید چه درباره متغیرهای محیطی؟ مطمئناً بهترین عمل این است که تنظیمات پیکربندی را در آنجا ذخیره کنید؟

اگر بخواهید، می‌توانید تنظیمات پیکربندی خود را در متغیرهای محیطی ذخیره کنید و آنها را مستقیماً از برنامه خود با استفاده از تابع os.Getenv() به این صورت دسترسی پیدا کنید:

addr := os.Getenv("SNIPPETBOX_ADDR")

اما این نسبت به استفاده از پرچم‌های خط فرمان دارای برخی معایب است. نمی‌توانید یک تنظیم پیش‌فرض مشخص کنید (مقدار بازگشتی از os.Getenv() رشته خالی است اگر متغیر محیطی وجود نداشته باشد)، شما عملکرد -help را که با پرچم‌های خط فرمان دارید، دریافت نمی‌کنید و مقدار بازگشتی از os.Getenv() همیشه یک رشته است — شما تبدیل نوع خودکار مانند flag.Int()، flag.Bool() و سایر توابع پرچم خط فرمان را دریافت نمی‌کنید.

در عوض، می‌توانید بهترین‌های هر دو جهان را با عبور دادن متغیر محیطی به عنوان یک پرچم خط فرمان هنگام شروع برنامه دریافت کنید. برای مثال:

$ export SNIPPETBOX_ADDR=":9999"
$ go run ./cmd/web -addr=$SNIPPETBOX_ADDR
2024/03/18 11:29:23 starting server on :9999

پرچم‌های بولی (Boolean Flags)

برای پرچم‌هایی که با flag.Bool() تعریف شده‌اند، حذف یک مقدار هنگام شروع برنامه همانند نوشتن -flag=true است. دو دستور زیر معادل هستند:

$ go run ./example -flag=true
$ go run ./example -flag

شما باید به صراحت از -flag=false استفاده کنید اگر می‌خواهید مقدار پرچم بولی را به false تنظیم کنید.

متغیرهای پیش‌فرض (Pre-existing Variables)

امکان تجزیه مقادیر پرچم خط فرمان به آدرس‌های حافظه متغیرهای پیش‌فرض با استفاده از flag.StringVar()، flag.IntVar()، flag.BoolVar() و توابع مشابه برای انواع دیگر وجود دارد.

این توابع به خصوص مفید هستند اگر بخواهید تمام تنظیمات پیکربندی خود را در یک ساختار واحد ذخیره کنید. به عنوان یک مثال تقریبی:

type config struct {
    addr      string
    staticDir string
}

...

var cfg config

flag.StringVar(&cfg.addr, "addr", ":4000", "HTTP network address")
flag.StringVar(&cfg.staticDir, "static-dir", "./ui/static", "Path to static assets")

flag.Parse()

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
تنظیمات پیکربندی Configuration Settings پارامترها و تنظیماتی که رفتار برنامه را کنترل می‌کنند
پرچم‌های خط فرمان Command-line Flags پارامترهایی که هنگام اجرای برنامه از طریق خط فرمان به آن منتقل می‌شوند
مقادیر پیش‌فرض Default Values مقادیری که در صورت عدم تعیین مقدار توسط کاربر استفاده می‌شوند
تبدیلات نوع Type Conversions تبدیل داده‌ها از یک نوع به نوع دیگر
متغیرهای محیطی Environment Variables متغیرهایی که در سطح سیستم عامل تعریف می‌شوند و می‌توانند توسط برنامه‌ها خوانده شوند
پرچم‌های بولی Boolean Flags پرچم‌هایی که فقط می‌توانند مقادیر درست یا نادرست داشته باشند
کمک خودکار Automated Help سیستم داخلی برای نمایش راهنمای استفاده از پرچم‌های خط فرمان
تنظیمات هاردکد شده Hardcoded Settings تنظیماتی که مستقیماً در کد برنامه نوشته شده‌اند و به راحتی قابل تغییر نیستند
آدرس شبکه Network Address آدرس و پورتی که سرور روی آن به درخواست‌ها گوش می‌دهد
ساختار پیکربندی Configuration Structure ساختار داده‌ای که تمام تنظیمات پیکربندی برنامه را در خود نگه می‌دارد