Dependency Injection ဘာလို့လုပ်?

Depdendency Injection အကြောင်းဖတ်ရရင် များသောအားဖြင့် ဘာလဲ၊ ဘယ်လိုလုပ်တာလောက်ပဲပြောပြကြတာတွေ့ရတယ်။ ဘာလို့လိုအပ်တယ်ဆိုတာကို သေချာမြင်အောင် ပြောပြဖို့လိုတယ်ထင်တယ်။ ဆိုတော့ ဘာလို့ဆိုတာမသွားခင် အခြေအနေတစ်ခုကို စဉ်းစားကြည့်ရအောင်။


နာမည်ကြီးရုပ်ရှင်ရုံကြီးဖြစ်တဲ့ Hello Cinema ဆိုတာရှိတယ်။ သူတို့မှာက အတွင်းမှာသုံးတဲ့ internal cashier system ကြီးရှိတယ်ဆိုပါဆို့ဗျာ။ ခက်တာက user app ရေးပေးမဲ့သူမရှိဘူးဗျ။ ဒီတော့ သူတို့က API spec ဘာညာအပြည့်အစုံနဲ့ ကျွန်တော်တို့စီရောက်လာတယ်။ Booking လုပ်ရလွယ်အောင် User App လေးရေးပေးပါပြောတယ်။ ကျွန်တော်တို့ကလည်းရေးပေးမယ်ပေါ့ဆိုပြီး ပြောလိုက်တယ်။​ ဒီလိုနဲ့ code တွေဘာတွေရေးပြီး ပထမဆုံးရလာတဲ့အခြေအနေမှာ ဒီလိုလေးရှိတယ်

NetworkClientFromHelloCinema ဆိုတာက Hello Cinema ကပေးထားတဲ့ API အတိုင်းရေးထားတဲ့ သူတို့ Hello Cinema ကြီးရဲ့ Server ကိုလှမ်းခေါ်၊ ပြီးရင် သူကပြန်လာတဲ့ data လေးကို သူတို့သတ်မှတ်ထားတဲ့ MovieDataFromHelloCinema အနေနဲ့သုံးရင်ရပြီပေါ့။ App လေးကို run လိုက်တယ်၊ အကုန်အဆင်ပြေနေတယ်။


ဒီလိုနဲ့ တစ်ပတ်လောက်ကြာတော့ Hello Cinema ဘက်ကပြောလာတယ်။ “ငါတို့ API နည်းနည်း အပြောင်းအလဲရှိတယ်၊ ဒါလေးပြောင်းသုံး” ဆိုပြီး API အသစ်ပေးလိုက်ပါလေရော။ MovieDataFromHelloCinema မှာအရင်ကပေးတဲ့ ပြမဲ့အချိန်ကို လူနားလည်တဲ့ string အနေနဲ့လာနေရာကနေ milisecond ကြီးပြောင်းသွားတယ်။ ဒီလို ပြောင်းမယ်ဆိုရင် ကိုယ်တွေဘက်ကကပြင်ရမဲ့ အချိန်ကို ပေးလိုက်တယ်။ အဲဒီမှာစတာပဲ။

“ဟင်၊ မင်းဟာက တစ်ပတ်တောင်ကြာမယ် ဟုတ်လား”

“အာ ဒုက္ခပါပဲ၊ ဒါမျိုးကြိုပြောလေ၊ ခုမှ ပြောင်းရင်တော်တော်ပြောင်းယူရမှာ။ ခင်ဗျားတို့ပြောတော့ အချိန် string ကဒီတိုင်းသုံးရင်ရပြီဆို။​ ကျွန်တော်တို့လည်း ပြရမဲ့ နေရာတိုင်း သုံးထားတာလေဗျာ။ အခုမှထပြောင်းမယ်ဆိုတော့ ကြာမှာပေါ့ကွ”

ဟိုဘက်ကလည်း “ဟင်၊ မင်းတို့ပြောတော့ system ကပြင်ရလွယ်အောင် ရေးထားတယ်ဆို၊​ ဘာလို့အခုကြတော့ ကြာနေတာတုန်း။ မရဘူး မြန်အောင်လုပ်ပေး”

ဒီလိုအငြင်းအခုန်တွေဖြစ်ပြီး အဆင်ပြေသွားတယ်ထားဦး၊ ဒီလိုမျိုးခဏခဏဖြစ်လာတဲ့အချိန်မှာ ကျွန်တော်တို့ဘက်က developer တွေနဲ့ တိုင်ပင်ရပြီ။​ “Hello Cinema ဘက်က API Developer ကကော သူဟာသူ ပြင်နေမှာပဲ။ ငါတို့သူတို့ဘက်ကပေးတဲ့ API ကိုမှီခိုလို့မရဘူး။ နောက်ပိုင်းငါတို့ဘက်က အမြန်ပြင်နိုင်အောင်တစ် ခုခုလုပ်ရမယ်ဟေ့။” ဆိုပြီးဖြစ်လာတယ်။​​ ဒါနဲ့ ကျွန်တော်တို့တွေ Presenter ပေါ်ကိုလာမထိအောင်လို့ ဒီလိုလေးခွဲရေးလိုက်မယ်။

ကိုယ်တွေရဲ့ကိုယ်ပိုင် DataSource၊ ကိုယ်ပိုင် data format ခွဲထားတော့ အခုဆိုရင် ဘယ်လိုဖြစ်ဖြစ် ပြင်ရေးလို့ရပြီ။ UI ပေါ်မှာလိုက်ပြင်စရာမလိုတော့ဘူး။ Data Source မှာ လိုတာရှိဝင်ပြင်၊ ဒါမှမဟုတ် data format နှစ်ခုကြားက mapping logic ကိုဝင်ပြင်။ အဲလောက်ဆို ရပြီ၊ တစ်ခုလုံးပြင်စရာမလိုတော့ဘူး။

Presenter မှာ ဒီလိုလေးတော့ ပြောင်းရတော့မယ်။ အရင်က NetworkClient ကိုခေါ်ထားရကနေ DataSource ပြောင်းခေါ်ရမယ်။

ဒါဆိုရင် Presenter မှာလည်း အိုကေဆိုပြေသွားပြီ။ ဒီလိုမျိုးပဲ ကျွန်တော်တို့ NetworkClient ကိုတိုက်ရိုက်ခေါ်ထားတဲ့နေရာအကုန်ကို အစားထိုးပြင်ရမယ်။ ပြင်ပြီးပြီဆိုရင်တော့ နောက်ပိုင်း Hello Cinema ဘက်က ပြင်တိုင်း ကျွန်တော်တို့ Data Source ရယ်၊ Data Mapping Logic ကိုပဲလာပြင်ရတော့မယ်။​ ကျန်တဲ့ UI အပိုင်းကို လာထိစရာမလိုဘူး။

ဒါကို ကျွန်တော်တို့က Dependency Inversion လို့ခေါ်တယ်။ System တစ်ခုလုံးက သူများ System ကို မှီခိုနေမဲ့အစား သူများ System က ကိုယ့် System အောက်ထဲရောက်နေအောင်လုပ်လိုက်တာပဲ။​ Presenter အနေနဲ့ကြည့်မယ်ဆိုလည်း Data ရတဲ့ နေရာကို တိုက်ရိုက်ယူမသုံးတော့ပဲ Data Source class သက်သက်ခွဲပြီး “ပြင်စရာရှိတဲ့အဲ့ထဲမှာပဲပြင်ဟေ့၊​ ငါ့ဆီ Data ရရင်ရပြီ” ဆိုပြီး မှီခိုနေရာကနေ ခွဲထုတ်ပလိုက်တာကို Dependency Inversion လို့ခေါ်တယ်။


ဒီလိုအဆင်ပြေနေရာကနေ တစ်ရက်ကျတော့ Test ရေးကြမယ်ဟေ့ဆိုပြီး Lead developer ကပြောလာရော။ Presenter ရဲ့ တာဝန်က data ယူပြီး view မှာပြဖို့ပဲဆိုတော့ စစ်ရမှာက

  1. Data ပါလာရင် showMovieListing ကိုခေါ်ရဲ့လား စစ်ရမယ်
  2. Error ဆိုရင် showError ကိုခေါ်ထားရဲ့လား စစ်မယ်။

ဒီမှာ Unit Test ရဲ့သဘောတရားအရ Data ဘယ်ကပဲလာလာ Presenter ရဲ့ တာဝန်ကိုပဲစစ်ရုံပဲ။ ပြီးတော့ ဒီအချိန်မှာ Internet ရှိစရာမလိုရဘူး။ ရှိလို့လည်းမရဘူး။​ မဟုတ်ရင် test fail ခဲ့ရင် လိုင်းကျနေလို့ဖြစ်တာလား မသဲမကွဲဖြစ်နိုင်တယ်၊ လိုင်းကျတယ် မကျဘူးဆိုတာ ကျွန်တော့်တို့ ထိန်းချုပ်လို့မရဘူး။ ကိုယ့်လက်အောက်ကမဟုတ်တဲ့ ထိန်းချုပ်လို့မရတဲ့အရာတစ်ခုကို Test လို့လည်းမရနိုင်ဘူး။ နောက်တစ်ချက်က Presenter ရဲ့ တာဝန်အရ သူက Data ဘယ်ကလာလဲသိစရာမလိုဘူး၊ Presenter ဘက်ကကြည့်ရင် ဘယ်ကလာလာ data ရရင်ရပြီ။ ပြီးတော့ Case က (၂) ခုစစ်ရမယ်ဆိုတော့ “Mock” ဖို့လိုလာပြီ။ Mock ဆိုတာက အစာထိုးတာကိုပြောတာပါ။ တကယ့် Hello Cinema ရဲ့ Server ကိုလှမ်းမခေါ်ပဲ လှမ်းခေါ်ပြီး ပြန်ရလာမဲ့ အခြေအနေတစ်ခုကို အစားထိုးတာကိုခေါ်တယ်။

ဒီတော့ ကျွန်တော်တို့ကြည့်မယ်ဆိုရင် MovieDataSource က Presenter ရဲ့ getMovieListingToPresent ထဲရောက်နေတယ်။

fun getMovieListingToPresent() {
  //Cannot access this outside from test
  val movieDataSource = MovieDataSource()
  
  //...
}

ဒီတော့ ဒါကို access လုပ်လို့ရအောင်ဆိုပြီး global variable အနေနဲ့ထုတ်ရေးလို့ရတယ်၊ function parameter နဲ့ ပေးလို့ရတယ်။ ဘာပိုကောင်းလဲဆို

  1. Global variable ထားခြင်းအားဖြင့် တကယ်လို့ ဒီ data source ထဲက တစ်ခြား function ကိုခေါ်မယ်ဆိုရင် same instance ကိုပဲပြန်သုံးလို့ရတယ်။ CPU/Memory မစားတော့ဘူးဗျာ။
  2. Function parameter ဆိုရင် view ကလှမ်းသုံးရင် View ပေါ်မှာ MovieDataSource တစ်ခုကို ခေါ်တိုင်းခေါ်တိုင်းအရင်ဆုံးဆောက်ပေးရမယ်၊​​ ဒါက မလိုအပ်ပဲ code ကို ပိုရှုပ်စေတယ်။ ပြီးတော့ ဘယ် data source လည်းပြောဖို့က View ရဲ့ တာဝန်လည်းမဟုတ်ဘူး၊ View ဆိုတာက user မြင်အောင် data ပြန်ပြဖို့ပဲ။
  3. Global variable ထားလိုက်တာက OOP မှာပါတဲ့ class တစ်ခုရဲ့ အတွင်းက ရှုပ်ထွေးတဲ့အပိုင်းတွေကို အပြင်ကို ပေးမသိခြင်းဆိုတဲ့ Encapsulation နဲ့လည်းကိုက်တယ်။

ဒီတော့ global variable ထားလိုက်မယ်။​ ပြီးရင် သူ့ကိုပြောင်းလို့ရအောင် setter ရေးပေးရတော့မယ်။ ဒါမှ test ထဲမှာလိုသလိုဖလှယ်လို့ရမှာကို။ ဒီတော့ ကျွန်တော်တို့ Presenter လေးထဲမှာ ဒီလိုလေးဖြစ်သွားပြီ


Test လို့ရပြီလားဆိုတော့ မရသေးဘူး။ MovieDataSource ကအခုချိန်မှာ “Concrete Implementation” ဖြစ်နေတယ်။ ကွန်ကရစ်တုံးကြီးလိုမျိုး အစားထိုးလို့မရအောင် ဖြစ်နေတယ်။ ဒါကိုကျွန်တော်တို့ က “Abstract Implementation” ဖြစ်အောင်ပြင်မှ အစားထိုးလို့ရတယ်။ “Abstract” ဆိုတာက တကယ်လက်တွေ့ဘာမှမရှိပဲ ဒါမျိုးဖြစ်သင့်တယ်လို့ဖော်ပြပေးတဲ့ idea လောက်ပဲ။ ဉပမာ ငါ့ကို “၁၀” ပေးဆိုရင် နံပါတ် “၁၀” ရရင်ရပြီ။ ၅ ကို ၂ နဲ့မြှောက်လို့ရတယ်၊ ၁၅ ထဲက ၅ နှုတ်ပေးလို့ရတယ်။

  • “၁၀”​ လိုတယ်ဆိုတာက Abstract၊ ဘယ်လိုပေးလို့မပြောဘူး
  • “၁၀ ရဖို့ ၅ ကို ၂ နဲ့မြှောက်ထားပါတယ်”​ဆိုတာက Concrete၊ ဘယ်လိုလုပ်ပြီး ဘာရတယ်ဆိုတာ အတိအကျကိုပါတယ်။

ဒါဆိုရင် ကျွန်တော်တို့ MovieDataSource ကို Abstract ဖြစ်အောင် ဘယ်လိုလုပ်မလဲဆို၊​ Abstract class ရေးလဲရတယ်၊​ interface ရေးလည်းရတယ်။ Abstract class ရေးရင် ဘယ်လို mock မလဲဆိုတော့ case တိုင်းအတွက် ဒီလို class လေးတွေရေးလို့ရတယ်။

Presenter မှာတော့ Type ကို ဒီလိုထားရမယ်။

movieDataSource variable က parent MovieDataSoure Type ဖြစ်ရပါမယ်ဆိုပြီး ပြောထားမှ ကျန်တဲ့ Fake ရေးထားတဲ့ class တွေအစားထိုးပေးလို့ရမယ်။ ဒါဆိုရင် test ဘက်ကနေပြီးတော့ setMovieDataSource ကိုခေါ်ပြီး အစားထိုးလို့ရပြီ။

ဒါ့မှမဟုတ် interface နဲ့ရေးမယ်ဆိုရင်

Presenter မှာလည်း အပေါ်ကလိုပြီး MovieDataSource type ပါဆိုပြီးထားပေးရမယ်။ Test မှာတော့ ဘယ်လိုပြောင်းသွားလဲဆိုတော့

ဒါကပိုကြည့်ကောင်းတယ်၊ test တဲ့ နေရာမှာတင် တစ်ခါတည်း​ လိုတဲ့ logic ကို ထည့်ရေးလို့ရတယ်။ class တွေလည်း ရှုပ်ပွမနေတော့ဘူးပေါ့။

ဒီလိုမျိုး “ဒီပုံစံ data ရရင်ရပြီ။ မင်းဘာသာမင်း fake ဖြစ်ဖြစ်၊ တကယ့် server ခေါ်တာဖြစ်ဖြစ် ငါဂရုမစိုက်ဘူး၊ ငါ့တာဝန်က မင်းပေးတာကိုယူပြီး view ကို ပြန်ပြပါလို့ပြောဖို့ပဲ” ဆိုပြီး Presenter က သတ်မှတ်လိုက်တာက Abstraction Layer တစ်ခုအလယ်ကခံလိုက်တာပဲ။ OOP အရဆို Polymorphism ခေါ်မလား။ ဘာကောင်းသွားလဲဆိုတော့

  1. Unit test ရေးလို့ရသွားပြီ။ Test က အရေးမကြီးဘူးလို့တော့ မပြောနဲ့နော်။ Test ရေးတဲ့ အကျိုးကျေးဇူးကအများကြီးရှိတယ်။
  2. Hello Cinema အစား World Cinema က လာပြီး App လုပ်ခိုင်းမယ်ဆိုလည်း WorldCinemaMovieDataSource ဆိုပြီး ပြောင်းရေးလိုက်ရင်ရပြီလေ။ ဒါမှမဟုတ်လည်း A/B Testing တွေလုပ်လို့ရတယ်။

ဘယ်လိုပဲရေးရေး ခုနက setMovieDataSource ဆိုပြီး သုံးလိုက်တာက Dependency Injection ပဲ။ ဒါကို Setter Injection လို့ခေါ်တယ်။ Presenter ကနေပြီး ငါသုံးမှာက ဒီ Data ပြန်ပေးနိုင်တဲ့ MovieDataSource တစ်ခုဖြစ်ရင်ရပြီ၊​ Hello Cinema ရဲ့ Server ကို တိုက်ရိုက်ခေါ်သုံးထားတဲ့ MovieDataSource concrete ကြီးကို မှီမခိုတော့ပဲ ငါ့ကို Data ပဲပေး၊ ငါကြည့်လုပ်မယ်ဆိုပြီး အပြင်က class တစ်ခုကို လာပြီး inject လုပ်ခွင့်ပေးလိုက်တယ်။


Setter Injection ရဲ့ မကောင်းတဲ့အချက်တစ်ခုက Entry point တွေများတာပဲ။ ပြောချင်တာက တကယ်လို့ ကိုယ်ရေးထားတာကို နားမလည်တဲ့ လူတစ်ယောက်က ဝင်ကြည့်ပြီး မလိုအပ်တဲ့နေရာတွေကနေ လှမ်းခေါ်ပြီး ပြင်သွားတယ်။ ဒါမှမဟုတ် သူ setter ကို ခေါ်ဖို့မေ့သွားတယ်ဆိုရင် Runtime ကျမှသိရမယ်။ Android မှာဆို တစ်ခါတစ်ခါ build ရင် ၃-၄ မိနစ်ဆိုရင် မလိုအပ်ပဲ အချိန်ကုန်တာပဲ။ ပိုဆိုးတာက သုံးလေးခုလောက်တစ်ပြိုင်နက် သုံးရပြီဆို ပထမ နှစ်ခုက တစ်နေရာ၊ နောက် နှစ်ခုက တစ်နေရာဆီက data source တွေဖြစ်သွားနိုင်တယ်။ ဒါဆို data တွေက မမှန်တော့ဘူး။ အဲဒါကြောင့်မလို့ တတ်နိုင်ရင် constructor ကို ပို့ပြီး ပြောင်းလို့မရအောင်သတ်မှတ်ရတယ်။ Kotlin မှာတော့ val ဆိုတာပေါ့။ java မှတော့ final ပေါ့။ final ဆိုတာက တစ်ခါ variable assignment လုပ်ပြီးသွားရင်ပြန်ပြင်လို့မရတော့ပါဘူး လို့ပြောတာပါ။ ဆိုတော့ ဘယ်လိုဖြစ်မလဲဆိုတော့

ဒါဆိုရင် ကျွန်တော်တို့ Presenter ကို စဆောက်ကတည်းက MovieDataSource တစ်ခုပဲ အသေဖြစ်တော့မယ်၊ ပြီးမှ ထပ်ပြောင်းလို့မရတော့ဘူး၊​​ Test မှာတော့ ဘယ်လိုပြောင်းလဲဆိုတော့

Setter ခေါ်မဲ့အစား constructor ထဲပြောင်းထည့်ပေးရတယ်။ ဒါကို Constuctor Injection လို့ခေါ်တယ်။ ကောင်းတာကတော့ Immutability ဆိုတဲ့ ဂုဏ်သတ္တိိကို ရသွားတယ်။ တစ်ခါထားပြီး ပြန်ပြင်လို့မရတာက Programmer တွေအတွက် trace လိုက်ရတာလွယ်တယ်။ မေ့သွားစရာလည်းမရှိဘူး၊​​ ဒါကို မထည့်ရင် compile လုပ်ကိုမရဘူး၊ Human error လျော့သွားတာပေါ့။ Code ရေးရင် ကိုယ့်ဟာကို ဆက်ရေးမဲ့သူက ငတုံးလို့သာ ယူဆပြီး ရေး၊ ခင်ဗျား code quality ကောင်းလာစေရမယ်။


အားလုံးအဆင်ပြေနေပေမဲ့ဗျာ၊​ တကယ်လို့ HelloCinemaDataSource က သူလည်း Dependency တွေထပ်လိုခဲ့ရင်ဘယ်လိုလုပ်မလဲ၊ ဉပမာ user ပေါ်မူတည်ပြီး စျေးလျော့ပေးမယ်၊ VIP ပဲကြည့်ရတဲ့ special movie တွေပြပေးရမယ်ဆိုပါဆို့၊ ဒါ့အပြင် သူရောက်နေတဲ့ မြို့ပေါ်လည်း မူတည်မယ်ဗျာ။ အောက်က code ကိုကြည့်ကြည့်

Presenter တစ်ခါဆောက်မယ်ဆို ပေးရမဲ့ class တွေက တော်တော်များတယ်။ Dependency ကတော့ ငြိမနေတော့ပါဘူ:။ ဒါပေမဲ့ တစ်ခါလိုတိုင်း class တွေပြန်ပြန်ဆောက်နေရတာ တော်တော်အချိန်ကြာစေမဲ့အလုပ်။ နည်းနည်းမြန်လာအောင်တော့ လွယ်လွယ်ကူကူ Key,Value Pair လေးနဲ့ ClassName နဲ့လိုအပ်တဲ့ တကယ့် class လတွေကို return ပြန်ပေးမဲ့ Provider အသေးစားလေးရေးလို့ရပါရဲ့။ ဒါပေမဲ့ ဒါကို automate လုပ်လို့ရရင် ပိုမမြန်ပေဘူးလားဆိုပြီ Dependency Injection Framework တွေကို အသုံးချကြတယ်။​ သူတို့မပါပဲလည်း ကိုယ့်ဟာကိုယ် manual လုပ်လို့ရတယ်ဆိုတာ သိစေချင်လို့ပါ။


ဒါဆိုရင် Dependency Inversion ဆိုတာက ဘာလဲ, Dependency Injection ဆိုတာဘာလဲ၊ ဘာလို့ framework တွေသုံးတာလဲဆိုတာ ကွဲကွဲပြားပြား မြင်ပြီနဲ့တူတယ်။ Framework မရှိပဲ Inject လို့ရတယ်၊ Injection မရှိပဲ Invert လို့ရတယ်။ မရောထွေးသွားပါစေနဲ့။ Inversion လုပ်လိုက်လို့ ကောင်းတာတွေကတော့

  • ခင်ဗျား System ကို ပြင်ရတာပိုမြန်လာမယ်​၊ မြန်လာရင် Product မှာ feature တွေအသစ်ရတာ ပိုမြန်လာမယ်၊ ပျက်ရင်လည်း ပြင်ရတာမြန်တော့ customer တွေပျော်ကြမယ်။
  • ကိုယ့် code ကို ငတုံးတွေပါ ဝင်ပြင်လို့ရလာမယ်။ ပြောချင်တာက Junior တွေကို onboard လုပ်ရင် အများကြီး ရှင်းပြစရာမလိုတော့ဘူး။ သူတို့ပြင်လိုက်လို့ အမှားပါသွားရင်လည်း အစိတ်အပိုင်းအကုန်လုံးကို မထိတော့ဘူး။
  • Layer လေးတွေရှိတော့ ကိုယ်တစ်ခုပြင်နေတုန်း ၊ တစ်ခြားတစ်ခုကို နောက်တစ်ယောက်က တစ်ပြိုင်းတည်း ပြင်လို့ရတယ်။ ဒါလည်း Resource ကို ပိုပြီး ထိရောက်အောင်သုံးတာပဲ။

တစ်ခြားအများကြီးရှိပါသေးတယ်။ လုပ်ကြည့်လိုက်ရင် ပိုမြင်သာလာပါလိမ့်မယ်။


အမှားပါသွားရင်လည်း ပြောပေးကြပါဦး။ ကြော်ငြာထိုးစရာဆိုလို့ ကျွန်တော်လုပ်နေတဲ့ Podcast လေးပဲရှိပါတယ်။​ နှစ်ပတ်တစ်ခါ သောကြာနေ့တိုင်း နည်းပညာအကြောင်းမှန်သမျှ ဆွေးနွေးနေပါတယ်။ Follow လေးလုပ်ပေးကြပါဦိးနော်။ နာမည်က Techshaw Cast တဲ့။

Techshaw Podcast
We named it by combining two words: Technology and Trishaw. In Yangon, trishaws can be seen almost everywhere. Take a…

*အသုံးပြုထားသော နာမည်များသည် ဉပမာ များသာဖြစ်သည်။ တကယ့်အဖွဲ့အစည်းမဟုတ်ပါ။