مدیریت تنظیمات پیکربندی
فایل main.go برنامه وب ما در حال حاضر شامل چند تنظیم پیکربندی سختکد شده است:
- آدرس شبکه برای گوش دادن سرور (در حال حاضر
":4000") - مسیر فایل برای دایرکتوری فایلهای استاتیک (در حال حاضر
"./ui/static")
سختکد کردن این موارد ایدهآل نیست. هیچ جدایی بین تنظیمات پیکربندی و کد ما وجود ندارد، و نمیتوانیم تنظیمات را در زمان اجرا (runtime) تغییر دهیم (که در صورت نیاز به تنظیمات مختلف برای محیطهای توسعه، تست و تولید مهم است).
در این فصل، ما شروع به بهبود این وضعیت میکنیم و با قابل تنظیم کردن آدرس شبکه سرور خود در زمان اجرا (runtime) شروع میکنیم.
پرچمهای خط فرمان
در Go، یک روش رایج در Go برای مدیریت تنظیمات پیکربندی، استفاده از پرچمهای خط فرمان هنگام راهاندازی یک برنامه است. به عنوان مثال:
$ go run ./cmd/web -addr=":80"
سادهترین روش برای پذیرش و تجزیه یک پرچم خط فرمان در برنامه شما با یک خط کد مانند این است:
addr := flag.String("addr", ":4000", "HTTP network address")
این اساساً یک پرچم خط فرمان جدید با نام addr، مقدار پیشفرض ":4000" و متن راهنمای کوتاهی که توضیح میدهد پرچم چه چیزی را کنترل میکند تعریف میکند. مقدار پرچم در زمان اجرا (runtime) در متغیر addr ذخیره میشود.
بیایید این را در برنامه خود استفاده کنیم و آدرس شبکه سختکد شده را با یک پرچم خط فرمان جایگزین کنیم:
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
مقادیر پیشفرض
پرچمهای خط فرمان کاملاً اختیاری هستند. به عنوان مثال، اگر برنامه را بدون پرچم -addr اجرا کنید، سرور به گوش دادن روی آدرس ":4000" بازمیگردد (که مقدار پیشفرض است که ما مشخص کردیم).
$ go run ./cmd/web 2024/03/18 11:29:23 starting server on :4000
هیچ قانونی در مورد اینکه چه چیزی را به عنوان مقادیر پیشفرض برای پرچمهای خط فرمان خود استفاده کنید وجود ندارد. من ترجیح میدهم از پیشفرضهایی استفاده کنم که برای محیط توسعه من منطقی هستند، زیرا در زمان ساخت یک برنامه در وقت و تایپ کردن صرفهجویی میکند. اما ممکن است نظر شما متفاوت باشد. ممکن است رویکرد امنتر تنظیم پیشفرضها برای محیط تولید خود را ترجیح دهید.
تبدیل نوع
در کد بالا، ما از تابع flag.String() برای تعریف پرچم خط فرمان استفاده کردهایم. این مزیت تبدیل هر مقداری که کاربر در زمان اجرا (runtime) ارائه میدهد به نوع string را دارد. اگر مقدار نتواند به string تبدیل شود، برنامه یک پیام خطا چاپ میکند و خارج میشود.
Go همچنین مجموعهای از توابع دیگر از جمله flag.Int()، flag.Bool()، flag.Float64() و flag.Duration() برای تعریف پرچمها دارد. اینها دقیقاً مانند flag.String() کار میکنند، با این تفاوت که به طور خودکار مقدار پرچم خط فرمان را به نوع مناسب تبدیل میکنند.
راهنمای خودکار
ویژگی عالی دیگر این است که میتوانید از پرچم -help برای فهرست کردن تمام پرچمهای خط فرمان موجود برای یک برنامه و متن راهنمای همراه آنها استفاده کنید. آن را امتحان کنید:
$ go run ./cmd/web -help
Usage of /tmp/go-build3672328037/b001/exe/web:
-addr string
HTTP network address (default ":4000")
بنابراین، در مجموع، این شروع به خوب شدن میکند. ما یک روش رایج در Go برای مدیریت تنظیمات پیکربندی برنامه خود در زمان اجرا (runtime) معرفی کردهایم، و به لطف پرچم -help، ما همچنین یک رابط صریح و مستند بین برنامه و پیکربندی عملیاتی آن داریم.
اطلاعات اضافی
متغیرهای محیطی
اگر قبلاً برنامههای وب ساخته و مستقر کردهاید، احتمالاً فکر میکنید متغیرهای محیطی چطور؟ مطمئناً بهترین روش ذخیره تنظیمات پیکربندی در آنجا است؟
اگر میخواهید، میتوانید تنظیمات پیکربندی خود را در متغیرهای محیطی ذخیره کنید و با استفاده از تابع 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
پرچمهای بولی
برای پرچمهایی که با flag.Bool() تعریف شدهاند، حذف مقدار هنگام راهاندازی برنامه همان نوشتن -flag=true است. دو دستور زیر معادل هستند:
$ go run ./example -flag=true $ go run ./example -flag
اگر میخواهید مقدار یک پرچم بولی را روی false تنظیم کنید، باید صریحاً از -flag=false استفاده کنید.
متغیرهای از پیش موجود
امکان تجزیه مقادیر پرچم خط فرمان به آدرسهای حافظه متغیرهای از پیش موجود با استفاده از 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()