Mini-Project: Typed Config Loader
ဒီ Mini-Project အကြောင်း
Section titled “ဒီ Mini-Project အကြောင်း”ရိုးရှင်းတဲ့၊ type မှန်ကန်တဲ့ configuration loader
(setting တွေကို သတ်မှတ်ပြီး အသုံးပြုဖို့ ပြင်ဆင်ပေးတဲ့စနစ်) တစ်ခု တည်ဆောက်ကြရအောင်။ ဒီစနစ်က ဘာတွေလုပ်ပေးမလဲဆိုတော့ -
- ကျွန်တော်တို့ရဲ့ App (Application) အတွက် Setting တွေ ဘယ်လိုပုံစံရှိရမယ်ဆိုတာကို အရင်ဆုံး သတ်မှတ်မယ်။
- ပြီးတော့ အပြင်ကနေ ရလာမယ့် Setting တွေကို ‘အတု’ ဖန်တီးပြီး ထည့်သွင်းမယ်။ (တကယ့် Project မှာဆိုရင် ဒါတွေက JSON file ထဲကနေ ဒါမှမဟုတ် Database ထဲကနေ လာနိုင်တယ်)။
- တစ်ခါတစ်လေ Setting တချို့က ပြည့်ပြည့်စုံစုံ မပါလာတတ်ဘူး။ အဲဒီအခါမျိုးမှာ မပါတဲ့ Setting တွေအတွက် ကျွန်တော်တို့ ကြိုတင်သတ်မှတ်ထားတဲ့ ‘Default Value’ (ပုံမှန်တန်ဖိုး) တွေ အစားထိုးပေးမယ်။
- နောက်ဆုံးရလာတဲ့ Setting တွေဟာ Type မှန်ကန်ကြောင်း (Type-safe ဖြစ်ကြောင်း) နဲ့ နောက်ထပ် ပြောင်းလို့မရအောင် (Immutable ဖြစ်အောင်) သေချာအောင် လုပ်ပေးမယ်။
အဆင့် ၁: Config ရဲ့ ပုံစံနဲ့ ‘Default Value’ တွေ သတ်မှတ်ခြင်း
Section titled “အဆင့် ၁: Config ရဲ့ ပုံစံနဲ့ ‘Default Value’ တွေ သတ်မှတ်ခြင်း”ပထမဆုံးအနေနဲ့ ကျွန်တော်တို့ App ရဲ့ Setting တွေ (Config) ဘယ်လိုပုံစံရှိရမယ်ဆိုတာကို interface နဲ့ သတ်မှတ်ပါမယ်။ ဒါက AppConfig လို့ ခေါ်ပါတယ်။
interface AppConfig { env: "development" | "production"; // ဒီ App ကို ဘယ်ပတ်ဝန်းကျင် (environment) မှာ run မလဲ။ (ဥပမာ- development အတွက်လား၊ production အတွက်လား) port: number; // ဒီ App က ဘယ် port နံပါတ်ကနေ အလုပ်လုပ်မလဲ။ (ဂဏန်းဖြစ်ရမယ်) apiBaseUrl: string; // API တွေကို ဆက်သွယ်မယ့် အဓိက URL (Link) ။ (စာသားဖြစ်ရမယ်) featureFlags?: Record<string, boolean>; // App ရဲ့ feature တွေကို ဖွင့်မလား/ပိတ်မလား ထိန်းချုပ်မယ့် flag တွေ။ (? ပါလို့ ထည့်ချင်မှ ထည့်လို့ရတဲ့ property ပါ)}
ပြီးတော့ Setting တွေ မပါလာရင် အစားထိုးပေးမယ့် ‘Default Value’ တွေ (ပုံမှန်တန်ဖိုးတွေ) ကို သတ်မှတ်ပါမယ်။ ဒီ defaultConfig ကို Partial<AppConfig> လို့ သတ်မှတ်ထားတာကို သတိထားပါ။
// ဒါတွေကတော့ Setting တွေအတွက် Default Value တွေပါ။// Partial<AppConfig> လို့ ဘာလို့ သတ်မှတ်လဲဆိုတော့ AppConfig ထဲက properties တွေ အကုန်မထည့်ဘဲ၊// ကျွန်တော်တို့ default ထားချင်တဲ့ properties တွေကိုပဲ ထည့်လို့ရလို့ပါ။const defaultConfig: Partial<AppConfig> = { env: "development", // `env` မပါရင် `development` လို့ ဖြစ်စေမယ်။ port: 3000, // `port` မပါရင် `3000` လို့ ဖြစ်စေမယ်။ featureFlags: { logging: true // `logging` feature ကို `true` (ဖွင့်ထား) လို့ default ထားမယ်။ }};
အဆင့် ၂: Config တွေကို load လုပ်ပေးမယ့် Function (Generics နဲ့ Utilities တွေကို သုံးပြီး)
Section titled “အဆင့် ၂: Config တွေကို load လုပ်ပေးမယ့် Function (Generics နဲ့ Utilities တွေကို သုံးပြီး)”အခုဆိုရင် ကျွန်တော်တို့ရဲ့ အဓိက ‘Config Loader’ Function ကို တည်ဆောက်ပါမယ်။ ဒီ Function ထဲမှာ Generics
နဲ့ Utility Types
တွေကို ဘယ်လို အသုံးချလဲဆိုတာ တွေ့ရမှာပါ။
// T က ကျွန်တော်တို့ App ရဲ့ Config ရဲ့ အပြည့်အစုံ ပုံစံ (ဥပမာ AppConfig) ပါ။// D က ကျွန်တော်တို့ရဲ့ Default Value တွေရဲ့ ပုံစံ (ဥပမာ Partial<AppConfig>) ပါ။function loadConfig<T extends object, D extends Partial<T>>( // Generic Types တွေဖြစ်တဲ့ <T, D> ကို ဒီမှာ သုံးထားတယ်။ loadedRawConfig: Partial<T>, // အပြင်ကနေ (file ထဲကနေ) ရလာတဲ့ Config တွေ။ သူတို့က အပြည့်အစုံ မဖြစ်နိုင်ဘူး။ defaultValues: D // ကျွန်တော်တို့ ကြိုတင်သတ်မှတ်ထားတဲ့ Default Value တွေ။ ): Readonly<T> { // ဒီ Function ကနေ ပြန်လာမယ့် result က Readonly (ပြောင်းလို့မရ) ဖြစ်ပြီး T (AppConfig) ရဲ့ အပြည့်အစုံ ပုံစံဖြစ်ရမယ်။
// ဒီ Function ရဲ့ အဓိက အလုပ်က Default Value တွေနဲ့ အပြင်ကနေ ရလာတဲ့ Setting တွေကို ပေါင်းစပ်ဖို့ပါပဲ။// အပြင်ကနေ ရလာတဲ့ Setting တွေမှာ default နဲ့ တူတဲ့ နာမည်ရှိရင် သူက default ကို အစားထိုးမယ် (ဦးစားပေးမယ်)။// ဒါကို 'shallow merge' (အပေါ်ယံ ပေါင်းစပ်ခြင်း) လို့ ခေါ်ပါတယ်။// ဥပမာ featureFlags လိုမျိုး nested object တွေဆိုရင်// အသေးစိတ် ပေါင်းတာ (deep merge) က ပိုကောင်းပေမယ့် ဒီဥပမာမှာ ရိုးရှင်းအောင် shallow merge ပဲ သုံးထားပါတယ်။const mergedConfig = {...defaultValues, // Default Value တွေ...loadedRawConfig, // အပြင်ကနေ ရလာတဲ့ Setting တွေ (ဒါတွေက default တွေပေါ်မှာ ထပ်သွားမယ်။)} as T; // 'as T' ကို ဘာလို့ သုံးလဲဆိုတော့ ဒီပေါင်းစပ်ထားတဲ့ object ဟာ နောက်ဆုံးမှာ T (AppConfig) Type နဲ့ ကိုက်ညီလိမ့်မယ်လို့ TypeScript ကို ပြောပြလိုက်တာပါ။// တကယ့် Project တွေမှာဆိုရင်တော့ Zod လိုမျိုး Library တွေနဲ့ ထပ်စစ်တာက ပိုပြီး Type မှန်ကန်မှုကို အာမခံနိုင်ပါတယ်။
// Object.freeze() ကို သုံးခြင်းဖြင့် mergedConfig object ကို ပြောင်းလဲလို့မရအောင် လုပ်လိုက်တာပါ။// ဒါဆိုရင် ဒီ object ရဲ့ properties တွေကို နောက်မှ ထပ်ပြောင်းလို့ ရတော့မှာ မဟုတ်ဘူး။return Object.freeze(mergedConfig); // final config ကို Readonly အဖြစ် ပြန်ပေး။}
loadConfig
Function ရှင်းပြချက်:
Section titled “loadConfig Function ရှင်းပြချက်:”- Generics (<T, D>):
T
ဆိုတာ ကျွန်တော်တို့ရဲ့ Config ရဲ့ အပြည့်အစုံ ပုံစံ (Type) ပါ။ ဥပမာAppConfig
လိုမျိုးပေါ့။D
ဆိုတာကတော့Default Value
တွေရဲ့ ပုံစံ (Type) ပါ။D
ကT
ရဲ့ တစ်စိတ်တစ်ပိုင်း (Partial) ဖြစ်ရမယ်လို့ သတ်မှတ်ထားတယ်။
- Input Parameters:
loadedRawConfig: Partial\<T>:
အပြင်ကနေ (JSON File ထဲကနေ) ရလာမယ့် Setting တွေပါ။ သူတို့က အမြဲတမ်း အပြည့်အစုံ ပါလာတာမျိုး မဟုတ်နိုင်လို့ Partial\<T>
လို့ သတ်မှတ်ထားတာပါ။defaultValues: D:
ကျွန်တော်တို့ ကြိုတင်သတ်မှတ်ထားတဲ့ ပုံမှန် (Default) တန်ဖိုးတွေပါ။
- Return Type:
Readonly\<T>
: ဒီ Function ကနေ ပြန်လာမယ့် နောက်ဆုံးရလဒ် (Config Object) ဟာT
(AppConfig) ရဲ့ အပြည့်အစုံ Type ဖြစ်ရမယ်။ ပြီးတော့Readonly
ဆိုတဲ့Utility Type
ကို သုံးထားလို့ ဒီ Object ကို ပြောင်းလဲလို့ ရတော့မှာ မဟုတ်ပါဘူး။ Object.freeze(mergedConfig)
: ဒါကmergedConfig
ဆိုတဲ့ Object ကို တကယ်ပဲ ပြောင်းလဲလို့မရအောင် လုပ်လိုက်တာပါ။ ဒါဆိုရင် ဒီ Object ရဲ့ properties တွေကို နောက်မှ ထပ်ပြောင်းလို့ ရတော့မှာ မဟုတ်ဘူး။
အဆင့် ၃: Config Loader ကို အသုံးပြုခြင်းနဲ့ satisfies ကို သုံးပြီး loaded config ကို စစ်ဆေးခြင်း
Section titled “အဆင့် ၃: Config Loader ကို အသုံးပြုခြင်းနဲ့ satisfies ကို သုံးပြီး loaded config ကို စစ်ဆေးခြင်း”အခု ကျွန်တော်တို့ တည်ဆောက်ထားတဲ့ loadConfig function ကို လက်တွေ့ အသုံးပြုကြည့်ရအောင်။
ပထမဆုံးအနေနဲ့၊ အပြင်ကနေ ရလာမယ့် config data ကို အတုအယောင် ဖန်တီးပါမယ်။ (တကယ့်အပြင်မှာဆိုရင် JSON file ကနေ လာနိုင်ပါတယ်)။ ဒီနေရာမှာ satisfies ကို ဘယ်လိုသုံးလဲဆိုတာ သတိထားပါ။
// ဒါက အပြင်ကနေ (ဥပမာ JSON file ကနေ) ရလာမယ့် configuration data ကို အတုအယောင် လုပ်ထားတာပါ။// ဒီ object က ပိုရှုပ်ထွေးနေရင် ဒါမှမဟုတ် code မှာ တိုက်ရိုက်ရေးရင် `satisfies` က အသုံးဝင်ပါတယ်။const configFileContent = {// env: "production", // ဒီ setting က file ထဲမှာ မပါဘူးလို့ စဉ်းစားကြည့်ရအောင်။port: 8080,apiBaseUrl: "/api/v2",featureFlags: {betaFeature: true,// logging setting က ဒီမှာ မပါဘူး၊ default ကနေ ယူသွားမယ်။},customServiceUrl: "http://my.service" // ဒီ property က AppConfig ထဲမှာ မပါပါဘူး။} satisfies Partial<AppConfig> & { customServiceUrl?: string }; // satisfies က Partial<AppConfig> နဲ့ ကိုက်ညီလားစစ်ပြီး၊ customServiceUrl လို အပိုတွေကိုလည်း ခွင့်ပြုတယ်။
// အကယ်၍ `AppConfig` မှာ ပါဝင်တဲ့ အစိတ်အပိုင်းတွေကိုပဲ သေချာအောင် စစ်ပြီး အပိုတွေကို ဖယ်ထုတ်ချင်ရင်-const filteredConfigFileContent: Partial<AppConfig> = {port: 8080,apiBaseUrl: "/api/v2",featureFlags: { betaFeature: true }};
// loadConfig function ကို သုံးပြီး filteredConfigFileContent နဲ့ defaultConfig တွေကို ပေါင်းစပ်လိုက်ပါမယ်။const finalAppConfig = loadConfig<AppConfig, Partial<AppConfig>>(filteredConfigFileContent, // ဒါမှမဟုတ် configFileContent ကို တိုက်ရိုက်ပေးလို့လည်း ရတယ်။defaultConfig);
console.log("နောက်ဆုံးရလာတဲ့ Config:");console.log(finalAppConfig);
// result တွေကို ကြည့်ရအောင်:// finalAppConfig.env ဟာ "development" (defaultConfig ကနေ ရလာ) ဖြစ်မယ်။// finalAppConfig.port ဟာ 8080 (configFileContent ကနေ ရလာ) ဖြစ်မယ်။// finalAppConfig.apiBaseUrl ဟာ "/api/v2" (configFileContent ကနေ ရလာ) ဖြစ်မယ်။// finalAppConfig.featureFlags ဟာ { betaFeature: true, logging: true } (ပေါင်းထားတာ၊ shallow merge ကို သတိပြုပါ) ဖြစ်မယ်။
// Type မှန်ကန်စွာ ခေါ်ယူသုံးစွဲခြင်း:console.log("ဒီ App ကို ဘယ် environment မှာ run နေလဲ:", finalAppConfig.env);console.log("API Base URL:", finalAppConfig.apiBaseUrl.toUpperCase());if (finalAppConfig.featureFlags?.betaFeature) {console.log("Beta feature ကို ဖွင့်ထားပါတယ်!");}
// finalAppConfig.port = 3001; // Error! ဒါက Readonly<AppConfig> ဖြစ်နေလို့ ပြောင်းလို့မရပါဘူး။// const customUrl = finalAppConfig.customServiceUrl; // Error! customServiceUrl က AppConfig ထဲမှာ မပါပါဘူး။
satisfies
အသုံးပြုပုံ ရှင်းပြချက်:
Section titled “satisfies အသုံးပြုပုံ ရှင်းပြချက်:”configFileContent
ကို သတ်မှတ်တဲ့အခါ၊satisfies Partial\<AppConfig> & { customServiceUrl?: string }
ကို သုံးခြင်းအားဖြင့်configFileContent
ဟာPartial\<AppConfig>
ရဲ့ ပုံစံနဲ့ ကိုက်ညီရဲ့လားဆိုတာ စစ်ပေးတယ်။- ဒါ့အပြင်
AppConfig
ထဲမှာ မပါဝင်ပေမယ့်customServiceUrl
လိုမျိုး အပို properties တွေကိုလည်း ဒီconfigFileContent
ထဲမှာ ထည့်ခွင့်ပြုထားတာဖြစ်ပါတယ်။