জেনে নিন কিভাবে ডিপেনডেন্সি ইনজেকশন কোডকে পরীক্ষাযোগ্য, রিফ্যাক্টর এবং সম্প্রসারণযোগ্য করে। ব্যবহারিক প্যাটার্ন, উদাহরণ ও সাধারণ ভুলগুলো সম্পর্কে জানুন।

ডিপেনডেন্সি ইনজেকশন (DI) একটা সহজ ধারণা: কোড নিজের প্রয়োজনীয় জিনিসগুলো নিজে তৈরি করার পরিবর্তে বাইরে থেকে পায়।
এগুলোই হল তার "ডিপেনডেন্সি"—উদাহরণস্বরূপ, ডাটাবেস কানেকশন, পেমেন্ট সার্ভিস, ক্লক, লগার বা ই-মেইল সেন্ডার। যদি আপনার কোড নিজের হাতেই এসব তৈরি করে, তাহলে সেটা চুপচাপ নির্ধারণ করে ফেলছে কিভাবে সেই ডিপেনডেন্সিগুলো কাজ করবে।
এক অফিসের কফি মেশিন ভাবুন। এটা পানি, কফি বিন, এবং বিদ্যুতের ওপর নির্ভরশীল।
DI ঐ দ্বিতীয় পদ্ধতি: “কফি মেশিন” (আপনার ক্লাস/ফাংশন) তার কাজ করবে, আর “সরবরাহ” (ডিপেনডেন্সি) যারা সেট আপ করেন তারা দিয়েই দেয়।
DI কোনো নির্দিষ্ট ফ্রেমওয়ার্ক ব্যবহার করার শর্ত নয়, এবং এটা DI কন্টেইনারের সমার্থকও নয়। আপনি ম্যানুয়ালি প্যারামিটার বা কনস্ট্রাকটরের মাধ্যমে DI করতে পারেন।
DI কে “মকিং” বলাও ঠিক হবে না—মকিং হচ্ছে টেস্টে DI ব্যবহার করার একটা উপায়, কিন্তু DI নিজে শুধুই ডিজাইন সিদ্ধান্ত যে ডিপেনডেন্সি কোথায় তৈরি হবে।
যখন ডিপেনডেন্সি বাইরের পক্ষ থেকে দেয়া হয়, আপনার কোড ভিন্ন প্রসঙ্গে চালানো সহজ হয়: প্রোডাকশন, ইউনিট টেস্ট, ডেমো, বা ভবিষ্যৎ ফিচার।
এই নমনীয়তা মডিউলগুলোকেও পরিষ্কার রাখে: অংশগুলো রিপ্লেস করা যায় পুরো সিস্টেমকে রিওয়্যার করার দরকার ছাড়া। ফলে টেস্টগুলো দ্রুত ও পরিষ্কার হয় (কারণ আপনি সহজ স্ট্যান্ড-ইন ব্যবহার করতে পারেন), এবং কোডবেস পরিবর্তন করা সহজ হয় (কারণ অংশগুলো কম জড়িত)।
টাইট কাপলিং তখনই ঘটে যখন কোডের একটি অংশ সরাসরি নির্ধারণ করে যে অন্য অংশগুলোকে কি ব্যবহার করতে হবে। সবচেয়ে সাধারণ রূপটি হলো: ব্যবসায়িক লজিকে new ব্যবহার করা।
ভাবুন একটি চেকআউট ফাংশন new StripeClient() এবং new SmtpEmailSender() নিজে তৈরি করছে। প্রথমে এটা সুবিধাজনক মনে হয়—প্রয়োজনীয় সবকিছু সেখানে রয়েছে। কিন্তু এটি চেকআউট ফ্লোকে সঠিক সেই ইমপ্লিমেন্টেশনের সাথে আটকে দেয়, কনফিগারেশন বিবরণ ও তাদের তৈরি-বিধি (API কী, টাইমআউট, নেটওয়ার্ক আচরণ) পর্যন্ত।
এই কাপলিং "লুকানো" কারণ এটা মেথড সিগনেচার থেকে স্পষ্ট হয় না। ফাংশনটি শুধু অর্ডার প্রসেস করে মনে হয়, কিন্তু গোপনে এটি পেমেন্ট গেটওয়ে, ইমেল প্রোভাইডার, এবং সম্ভবত ডাটাবেসের ওপর নির্ভরশীল।
যখন ডিপেনডেন্সি হার্ড-কোডেড থাকে, ছোট পরিবর্তনও রিপল эффেক্ট ফেলে:
হার্ড-কোডেড ডিপেনডেন্সি ইউনিট টেস্টকে বাস্তব কাজ চালাতে বাধ্য করে: নেটওয়ার্ক কল, ফাইল I/O, ঘড়ি, র্যান্ডম আইডি, বা শেয়ার করা রিসোর্স। টেস্টগুলো আইসোলেটেড না থাকায় ধীরে চলে এবং ফ্ল্যাকি হয় কারণ ফলাফল টাইমিং, বাহ্যিক সার্ভিস, বা এক্সিকিউশনের অর্ডারের ওপর নির্ভর করে।
যদি নিচের প্যাটার্নগুলো দেখতে পান, টাইট কাপলিং ইতিমধ্যে আপনার সময় খাওয়াচ্ছে:
newডিপেনডেন্সি ইনজেকশন এই সমস্যাগুলো সমাধান করে—ডিপেনডেন্সিগুলোকে স্পষ্ট ও সহজে বদলযোগ্য করে দেয়, ব্যবসায়িক নিয়ম পুনর্লিখন ছাড়া।
ইনভার্সন অফ কন্ট্রোল (IoC) হল দায়বদ্ধতার একটি সরল পরিবর্তন: একটি ক্লাসকে কি করতে হবে সে বিষয়ে মনোনিবেশ করা উচিত, না কিভাবে প্রয়োজনীয় জিনিসগুলো পাবে।
যখন একটি ক্লাস নিজের ডিপেনডেন্সি ক্রিয়েট করে (যেমন new EmailService() বা সরাসরি ডাটাবেস কানেকশন ওপেন করা), তখন এটি চুপচাপ দুইটা কাজ করছে: ব্যবসায়িক লজিক এবং সেটআপ। এতে ক্লাসটি বদলাতে, পুনঃব্যবহার করতে এবং টেস্ট করতে কঠিন হয়ে যায়।
IoC-তে আপনার কোড অ্যাবস্ট্রাকশনের ওপর নির্ভর করে—ইন্টারফেস বা ছোট "চুক্তি" টাইপের ওপর—কোনো নির্দিষ্ট ইমপ্লিমেন্টেশনের ওপর নয়।
উদাহরণস্বরূপ, একটি CheckoutService-এর দরকার নেই জানা যে পেমেন্ট প্রক্রিয়াকরণ Stripe, PayPal, না একটা টেস্ট প্রসেসর দিয়ে হবে—ওয়ের কেবল দরকার "কেউ যে কার্ড চার্জ করতে পারে"। যদি CheckoutService একটি IPaymentProcessor নেয়, তাহলে এটি যেকোনো ইমপ্লিমেন্টেশন দিয়ে কাজ করবে যা ঐ চুক্তি মেনে চলে।
এটা আপনার কোর লজিককে স্থিতিশীল রাখে এমনকি যখন আন্ডারলাইং টুলস বদলে যায়।
IoC-র ব্যবহারিক অংশ হলো ডিপেনডেন্সি ক্রিয়েশন ক্লাসের বাইরে নিয়ে আসা এবং সেগুলো পাস করা (অften কনস্ট্রাকটরের মাধ্যমে)। এটিই ডিপেনডেন্সি ইনজেকশন (DI)-এর কাজ: IoC অর্জনের একটি সাধারণ উপায়।
এর বদলে:
আপনি পান:
ফলাফল হলো নমনীয়তা: আচরণ বদলানো একটি কনফিগারেশন সিদ্ধান্ত হয়ে যায়, রিরাইট নয়।
যদি ক্লাসগুলো তাদের ডিপেনডেন্সি তৈরি না করে, তখন আরেকটি জিনিস করতে হবে—সেই জিনিসটিই হলো কম্পোজিশন রুট: যেখানে আপনার অ্যাপ অ্যাসেম্বল করা হয়—সাধারণত স্টার্টআপ কোড।
কম্পোজিশন রুটে আপনি নির্ধারণ করবেন, “প্রোডাকশনে RealPaymentProcessor; টেস্টে FakePaymentProcessor।” ওয়্যারিং এক জায়গায় রাখা বিস্ময় কমায় এবং বাকি কোডবেসকে আচরণে ফোকাস রাখতে সাহায্য করে।
IoC ইউনিট টেস্টগুলো সহজ করে কারণ আপনি রিয়াল নেটওয়ার্ক বা ডাটাবেসের বদলে ছোট, দ্রুত টেস্ট ডাবল দিতে পারেন।
এছাড়া রিফ্যাক্টরিংও নিরাপদ হয়: যখন দায়িত্বগুলো পৃথক থাকে, একটি ইমপ্লিমেন্টেশন বদলালে সাধারণত যেগুলো ব্যবহার করে তাদের বদলাতে হয় না—যতক্ষণ না অ্যাবস্ট্রাকশন পরিবর্তিত হয়।
DI কোনো একক কৌশল নয়—এটি কিছু ছোট উপায়ের সেট যাতে আপনি একটি ক্লাসকে তার দরকারি জিনিসগুলো (যেমন লগার, ডাটাবেস ক্লায়েন্ট, বা পেমেন্ট গেটওয়ে) "খাওয়ান"। আপনি যে স্টাইল পছন্দ করবেন তা স্পষ্টতা, পরীক্ষাযোগ্যতা ও অপব্যবহারের সম্ভাব্যতা প্রভাবিত করে।
কনস্ট্রাকটর ইনজেকশনে, ডিপেনডেন্সিগুলো অবজেক্ট তৈরি করার সময় আবশ্যক। বড় সুবিধা: আপনি ভুলবশত সেগুলো ভুলে যেতে পারবেন না।
এটা সবচেয়ে ভালো যখন একটি ডিপেনডেন্সি:
কনস্ট্রাকটর ইনজেকশন পরিষ্কার কোড এবং সরাসরি ইউনিট টেস্ট দেয়—টেস্টে আপনি ফেক বা মক কনস্ট্রাকশনের সময়ই পাস করতে পারেন।
কখনও কখনও একটি ডিপেনডেন্সি কেবল একটা অপারেশনের জন্যই দরকার—উদাহরণ: একটি অস্থায়ী ফরম্যাটার, বিশেষ স্ট্র্যাটেজি, বা রিকোয়েস্ট-স্কোপড ভ্যালু।
এই ক্ষেত্রে, সেটি মেথড প্যারামিটার হিসাবে পাস করুন। এটি অবজেক্টকে ছোট রাখে এবং একবারের চাহিদাকে স্থায়ী ফিল্ড করে না।
সেটার ইনজেকশন সুবিধাজনক হতে পারে যখন আপনি কনস্ট্রাকশন সময় ডিপেনডেন্সি দিতে পারছেন না (কিছু ফ্রেমওয়ার্ক বা লেগেসি কোড পাথ)। বদলে—এটি প্রয়োজনীয়তা লুকিয়ে রাখে: ক্লাসটি ব্যবহারযোগ্য দেখায় এমনকি পুরো কনফিগার করা না থাকলেও।
এটা প্রায়শই রানটাইম-এর বিস্ময় ডেকে আনে ("কেন এটা undefined?"), এবং টেস্টগুলোকে দুর্বল করে কারণ সেটআপ ভুলে যাওয়া সহজ।
ইউনিট টেস্ট তখন সবচেয়ে কার্যকর যখন সেগুলো দ্রুত, পুনরাবৃত্তিযোগ্য এবং এক বিহেভিয়ারের ওপর কেন্দ্রীভূত। টেস্ট যদি বাস্তব ডাটাবেস, নেটওয়ার্ক কল, ফাইলসিস্টেম, বা ক্লক নির্ভর করে, তা ধীর হয়ে যায় এবং ফ্ল্যাকি হয়ে পড়ে। আরও খারাপ হলো ব্যর্থতা অস্পষ্ট—কোড নাকি পরিবেশ কি ভেঙেছে?
DI এটি প্রতিরোধ করে কারণ আপনার কোড বহিরাগত ডিপেনডেন্সি বাইরের থেকে গ্রহণ করে। টেস্টগুলোতে আপনি সেগুলো বদলে লাইটওয়েট সাবস্টিটিউট ইনজেক্ট করতে পারেন।
বাস্তব DB বা API কল সেটআপ টাইম ও লেটেন্সি যোগ করে। DI দিয়ে আপনি ইন-মেমরি রিপোজিটরি বা প্রস্তুত রেসপন্স দেয় এমন ফেক ক্লায়েন্ট ইনজেক্ট করতে পারেন। ফলে:
DI না থাকলে কোড প্রায়ই নিজের ডিপেনডেন্সি তৈরি করে, টেস্টকে পুরো স্ট্যাক এক্সারসাইজ করাতে বাধ্য করে। DI দিয়ে আপনি ইনজেক্ট করতে পারেন:
কোনও হ্যাক নয়, কোনও গ্লোবাল সুইচ নয়—শুধু ভিন্ন ইমপ্লিমেন্টেশন পাস করুন।
DI সেটআপকে স্পষ্ট করে। কনফিগারেশন, কানেকশন স্ট্রিং, বা টেস্ট-অনলি এনভায়রনমেন্ট ভ্যারিয়েবলগুলো খুঁটিয়ে খুঁজে বের করার বদলে, আপনি একটি টেস্ট পড়েই বুঝে ফেলতে পারবেন কি বাস্তব কি মক।
একটি সাধারণ DI-ফ্রেন্ডলি টেস্ট পড়ে এমন হয়:
এই সরলতা শব্দবর্জিতিকে কমায় এবং ব্যর্থতাগুলো নির্ণয় সহজ করে—ঠিক যেটা আপনি ইউনিট টেস্ট থেকে চান।
একটি টেস্ট সীম হলো আপনার কোডে ইচ্ছাকৃতভাবে একটি "খোলা জায়গা" যেখানে আপনি একটি আচরণ বদলে দিতে পারেন। প্রোডাকশনে এখানে বাস্তব জিনিস লাগান; টেস্টে নিরাপদ, দ্রুত সাবস্টিটিউট লাগান। DI এই সীমগুলো তৈরি করার সহজ উপায়গুলোর একেকটি—বিনা হ্যাকের।
সীমগুলো সবচেয়ে উপকারী এমন অংশগুলোর চারপাশে থাকে যা টেস্টে নিয়ন্ত্রণ করা কঠিন:
যদি আপনার ব্যবসায়িক লজিক সরাসরি এসব কল করে, টেস্ট ঝুঁকিপূর্ণ হয়: নেটওয়ার্ক হিকআপ, টাইমজোন ভিন্নতা, মিসিং ফাইল—এসব কারণেই ব্যর্থতা হতে পারে এবং এগুলো টেস্টকে ধীর করে।
একটি সীম প্রায়ই একটি ইন্টারফেস রূপ নেয়—বা ডাইনামিক ভাষায় একটি সরল চুক্তি যেমন "অবজেক্টটির একটি now() মেথড থাকতে হবে"। মূল ধারণা: আপনি কি লাগে তা নির্ভর করুন, কোথা থেকে আসে তা নয়।
উদাহরণ: একটি অর্ডার সার্ভিসে সরাসরি সিস্টেম ক্লক কল করার বদলে আপনি Clock ব্যবহার করতে পারেন:
SystemClock.now()FakeClock.now() নির্দিষ্ট সময় রিটার্ন করেইয়েই প্যাটার্ন ফাইল রিড (FileStore), ইমেল পাঠানো (Mailer) কিংবা কার্ড চার্জিং (PaymentGateway)-এর জন্যও কাজ করে। কোর লজিক অপরিবর্তিত থাকে; কেবল প্লাগ-ইন ইমপ্লিমেন্টেশন বদলে যায়।
যখন আপনি আচরণ ইচ্ছে করে বদলাতে পারেন:
ভালোভাবে স্থাপিত সীম হেভি মকিংয়ের প্রয়োজন কমিয়ে দেয়। বদলে, আপনি কয়েকটি পরিষ্কার সাবস্টিটিউশন পয়েন্ট পান যা ইউনিট টেস্টকে দ্রুত, ফোকাসড এবং পূর্বানুমেয় রাখে।
মডিউলারিটি মানে সফটওয়্যার স্বতন্ত্র অংশ (মডিউলগুলি) থেকে গঠিত—প্রত্যেক মডিউলের সুস্পষ্ট দায়িত্ব ও বাউন্ডারি থাকে।
DI এই বাউন্ডারিগুলো স্পষ্ট করে দেয়। একটি মডিউল সবকিছু তৈরি বা খুঁজে না করে শুধুই তার ডিপেনডেন্সি পায় বাইরের থেকে। এই ছোট পরিবর্তন কমিয়ে দেয় একটি মডিউল আরেকটিকে কতটা জানে।
যখন কোড ভিতরে থেকে ডিপেনডেন্সি তৈরি করে (উদাহরণ: সার্ভিসের ভিতরে ডাটাবেস ক্লায়েন্ট new করা), কলার ও ডিপেনডেন্সির মধ্যে শক্ত কাপলিং তৈরি হয়। DI আপনাকে ইন্টারফেস (বা সরল চুক্তি) নির্ভর করতে উৎসাহিত করে, কনক্রিট ইমপ্লিমেন্টেশনের ওপর নয়।
এতে একটি মডিউল সাধারণত জানে:
PaymentGateway.charge())ফলস্বরূপ, মডিউলগুলো একসাথে কম বদলায়, কারণ অভ্যন্তরীণ বিবরণ বাউন্ডারি ছাড়িয়ে লিক করে না।
একটি মডিউলার কোডবেসে আপনি একটি কম্পোনেন্ট বদলাতে পারেন কলারদের পুনরায় লেখা ছাড়া। DI এটা বাস্তবে পরিণত করে:
প্রতিটি ক্ষেত্রে কলার একই চুক্তি ব্যবহার করে। ওয়্যারিং এক জায়গায় (কম্পোজিশন রুট) বদলে যায়, না যে পুরো কোডবেসে ছড়িয়ে পরিবর্তন করতে হবে।
স্পষ্ট ডিপেনডেন্সি বাউন্ডারিগুলো টিমগুলোকে প্যারালালভাবে কাজ করতে সাহায্য করে। একটি দল একটি চুক্তির পেছনে নতুন ইমপ্লিমেন্টেশন তৈরি করতে পারে অন্য দল যখন ঐ ইন্টারফেস ব্যবহার করে ফিচার উন্নয়ন চালিয়ে যায়।
DI ইনক্রিমেন্টাল রিফ্যাক্টরিংও সহজ করে: আপনি একটি মডিউল বের করে ইনজেক্ট করতে পারেন এবং ধাপে ধাপে রিপ্লেস করতে পারেন—বিগ-ব্যাং রিরাইট ছাড়া।
কোডে DI দেখলে ধারণা দ্রুত ক্লিক করে। এখানে ছোট একটি নোটিফিকেশন ফিচারের "পূর্ব" ও "পর" উদাহরণ।
যখন ক্লাস ভিতরে new কল করে, তখন এটি সিদ্ধান্ত নেয় কোন ইমপ্লিমেন্টেশন ব্যবহার হবে এবং কিভাবে সেটি তৈরি হবে।
class EmailService {
send(to, message) {
// talks to real SMTP provider
}
}
class WelcomeNotifier {
notify(user) {
const email = new EmailService();
email.send(user.email, "Welcome!");
}
}
টেস্টিং সমস্যা: একটি ইউনিট টেস্ট বাস্তবে ইমেল ইউজ করতে পারে (অথবা গ্লোবাল স্টাবিং ছাড়া কঠিন)।
test("sends welcome email", () => {
const notifier = new WelcomeNotifier();
notifier.notify({ email: "[email protected]" });
// Hard to assert without patching EmailService globally
});
এখন WelcomeNotifier যেকোনো অবজেক্ট গ্রহণ করে যা প্রয়োজনীয় আচরণ মেনে চলে।
class WelcomeNotifier {
constructor(emailService) {
this.emailService = emailService;
}
notify(user) {
this.emailService.send(user.email, "Welcome!");
}
}
টেস্ট ছোট, দ্রুত, এবং স্পষ্ট হয়ে ওঠে।
test("sends welcome email", () => {
const fakeEmail = { send: vi.fn() };
const notifier = new WelcomeNotifier(fakeEmail);
notifier.notify({ email: "[email protected]" });
expect(fakeEmail.send).toHaveBeenCalledWith("[email protected]", "Welcome!");
});
পরবর্তীতে SMS চান? WelcomeNotifier-এ যাওয়ার দরকার নেই। আপনি কেবল ভিন্ন ইমপ্লিমেন্টেশন পাস করবেন:
const smsService = { send: (to, msg) => {/* SMS provider */} };
const notifier = new WelcomeNotifier(smsService);
প্রায়োগিক ফলাফল: টেস্টগুলো কনস্ট্রাকশন বিস্তারিত নিয়ে লড়াই বন্ধ করে, এবং নতুন আচরণ যোগ করা হয় ডিপেনডেন্সি বদলিয়ে, না যে বিদ্যমান কোড রিরাইট করে।
DI হতে পারে সহজতর: "আপনি যা দরকার তা ব্যবহারকারীকে পাস করুন।" এটিই ম্যানুয়াল DI। DI কন্টেইনার সেই ওয়্যারিং অটোমেট করে। উভয়ই উপযুক্ত—কিন্তু উপযুক্ত স্তর বাছাই করাটাই মূল।
ম্যানুয়াল DI-তে আপনি নিজে অবজেক্ট তৈরি করে কনস্ট্রাকটরে ডিপেনডেন্সি পাস করেন। এর সুবিধা:
ম্যানুয়াল ওয়্যারিং ভালো ডিজাইন হাবিটও জোর দেয়। যদি কোনো অবজেক্ট সাতটি ডিপেনডেন্সি চায়, সেটি তাড়াতাড়ি অসুবিধাজনক মনে হবে—এটি প্রায়ই ক্লাস ভাগ করার সঙ্কেত।
কম্পোনেন্ট বাড়লে ম্যানুয়াল ওয়্যারিং পুনরাবৃত্তির ঝামেলায় পরতে পারে। DI কন্টেইনার সহায়তা করে:
কন্টেইনার সেইসব অ্যাপ্লিকেশনে ভাল যেখানে পরিষ্কার বাউন্ডারি ও লাইফসাইকেল আছে—ওয়েব অ্যাপ, লং-রানিং সার্ভিস, বা যেখানে অনেক ফিচার শেয়ার করা ইনফ্রাস্ট্রাকচারের ওপর নির্ভরশীল।
কন্টেইনার একটা ভারসাম্যহীন ডিজাইনকে গোছা মনে করিয়ে দিতে পারে কারণ ওয়্যারিং দেখা যায় না। কিন্তু মুল ইস্যুগুলো রয়ে যায়:
যদি একটি কন্টেইনার কোডকে কম পাঠযোগ্য করে দেয়, বা ডেভেলপাররা কি নির্ভর করে কি জানানো বন্ধ করে দেয়—তাহলে আপনি সম্ভবত অতিরিক্ত করছেন।
শুরু করুন ম্যানুয়াল DI দিয়ে যাতে জিনিসগুলো স্পষ্ট থাকে যখন আপনি আপনার মডিউলগুলো গঠন করছেন। কন্টেইনার যোগ করুন যখন ওয়্যারিং পুনরাবৃত্তিমূলক হয়ে ওঠে বা লাইফসাইকেল ম্যানেজমেন্ট জটিল হয়।
একটি ব্যবহারিক নিয়ম: কোর/বিজনেস কোডের ভিতরে ম্যানুয়াল DI ব্যবহার করুন, এবং (ঐচ্ছিকভাবে) অ্যাপ বাউন্ডারিতে একটি কন্টেইনার রাখুন যাতে সবকিছু অ্যাসেম্বল করা যায়। এভাবে ডিজাইন স্পষ্ট থাকে এবং প্রোজেক্ট বাড়ার সঙ্গে বয়লারপ্লেট কমে।
DI কোডকে টেস্ট এবং পরিবর্তন করা সহজ করতে পারে—কিন্তু শৃঙ্খলা না থাকলে তা ব্যর্থও হতে পারে। নিচে সবচেয়ে সাধারণ ভুলগুলো ও সেগুলো এড়ানোর অভ্যাস।
যদি কোনো ক্লাস অনেক ডিপেনডেন্সি চায়, সম্ভবত সেটা অনেক বেশি কাজ করছে। এটা DI-এর ব্যর্থতা নয়—DI ডিজাইনের গন্ধ উদঘাটন করছে।
প্রায়োগিক নিয়ম: যদি আপনি ক্লাসের কাজ এক বাক্যে বর্ণনা করতে না পারেন, বা কনস্ট্রাকটর বাড়তে থাকে, ক্লাস ভাগ করুন, ছোট সহযোগী বের করুন, বা অনুরূপ অপারেশনগুলোকে এক ইন্টারফেসের পেছনে গ্রুপ করুন (সাবধানে—"গড সার্ভিস" তৈরি করবেন না)।
সার্ভিস লোকেটর প্যাটার্ন সাধারণত ব্যবসায়িক কোডের ভিতরে container.get(Foo) দেখতে পাওয়া যায়। এটি সুবিধাজনক লাগে, কিন্তু এটি ডিপেনডেন্সি অদৃশ্য করে: কনস্ট্রাকটর পড়ে বোঝা যায় না ক্লাসের কী দরকার।
টেস্ট করা কঠিন হয় কারণ গ্লোবাল স্টেট (লোকেটর) সেটআপ করতে হয়; স্পষ্ট কনস্ট্রাকটর প্যারামিটারগুলো দিয়ে টেস্টের অভিপ্রায় বোঝা যায়।
DI কন্টেইনারগুলো রানটাইমে ব্যর্থ হতে পারে যখন:
এই সমস্যা অপ্রস্তুতিতে বিরক্তিকর কারণ সেগুলো কেবল যখন ওয়্যারিং এক্সিকিউট হয় তখনই দেখা দেয়।
কনস্ট্রাকটরগুলো ছোট ও ফোকাসড রাখুন। যদি ডিপেনডেন্সি তালিকা বাড়ে, তা রিফ্যাক্টরের নির্দেশ মনে করুন।
ওয়্যারিংয়ের জন্য ইন্টিগ্রেশন টেস্ট রাখুন। একটি হালকা "কম্পোজিশন রুট" টেস্ট যে আপনার কন্টেইনার (বা ম্যানুয়াল ওয়্যারিং) বিল্ড করে তা মিসিং রেজিস্ট্রেশন এবং সার্কেল দ্রুত ধরবে—প্রোডাকশনের আগে।
শেষে, অবজেক্ট তৈরি এক জায়গায় (অften অ্যাপ স্টার্টআপ/কম্পোজিশন রুট) রাখুন এবং DI কন্টেইনার কলগুলো ব্যবসায়িক লজিক থেকে দূরে রাখুন। এই পৃথকরণ DI-র মূল সুবিধা রক্ষা করে: কি কী-র ওপর নির্ভর করে তা স্পষ্ট রাখা।
DI গ্রহণ করা সহজ যখন আপনি এটিকে ছোট, কম-ঝুঁকিপূর্ণ রিফ্যাক্টরগুলোর সিরিজ হিসেবে দেখেন। সেখানে শুরু করুন যেখানে টেস্ট ধীর বা ফ্ল্যাকি, এবং যেখানে পরিবর্তন নিয়মিতভাবে অপ্রাসঙ্গিক কোডে ছড়িয়ে পড়ে।
তালিকা খুঁজুন যেগুলো টেস্ট করা কঠিন বা বোঝা কঠিন:
যদি কোনো ফাংশন প্রসেস ছাড়া চালাতে না পারে, সেটা DI-র ভালো প্রার্থী।
new করা হচ্ছে বা সরাসরি কল হচ্ছে।এই পদ্ধতি প্রতিটি পরিবর্তন রিভিউযোগ্য রাখে এবং যেকোনো ধাপে থেমে থাকা যায় ছাড়া সিস্টেম ভাঙা ছাড়া।
DI ভুলভাবে ব্যবহৃত হলে কোড "সবকিছুই সবকিছুর ওপর নির্ভরশীল" হয়ে যেতে পারে যদি আপনি অত্যধিক ইনজেক্ট করেন।
ভালো নিয়ম: ক্যাপাবিলিটি ইনজেক্ট করুন, বিস্তারিত নয়। উদাহরণ: Clock ইনজেক্ট করুন, না যে "SystemTime + TimeZoneResolver + NtpClient"। যদি একটি ক্লাস পাঁচটি অপ্রাসঙ্গিক সার্ভিস চায়, এটি সম্ভবত অনেক কাজ করছে—বিভাজন বিবেচনা করুন।
আরও, অনেক লেয়ারে প্যাস-থ্রু করে ডিপেনডেন্সি না পাঠান “শুধু কেসে”; যেখানে ব্যবহার হচ্ছে সেখানে ইনজেক্ট করুন; ওয়্যারিং এক জায়গায় কেন্দ্রীভূত রাখুন।
আপনি যদি কোড জেনারেটর ব্যবহার করেন বা দ্রুত ফিচার বানাতে স্ক্যাফোল্ড করেন, DI আরও মূল্যবান হয়ে ওঠে কারণ এটি প্রকল্প বড় হলে কাঠামো রক্ষা করে। উদাহরণস্বরূপ, যখন টিমগুলি Koder.ai ব্যবহার করে React ফ্রন্টএন্ড, Go সার্ভিস, এবং PostgreSQL-ব্যাকড ব্যাকএন্ড জেনারেট করে ছোট স্পেক থেকে, একটি স্পষ্ট কম্পোজিশন রুট ও DI-ফ্রেন্ডলি ইন্টারফেস রাখলে জেনারেটেড কোড টেস্ট, রিফ্যাক্টর এবং ইন্টিগ্রেশন বদলানো সহজ থাকে।
নিয়ম একই: অবজেক্ট তৈরি ও পরিবেশ-নির্দিষ্ট ওয়্যারিং বর্ডারে রাখুন, আর ব্যবসায়িক কোড আচরণে ফোকাস রাখুক।
আপনি স্পষ্ট উন্নতি নির্দেশ করতে সক্ষম হওয়া উচিত:
পরবর্তী ধাপ হলে, আপনার কম্পোজিশন রুট ডকুমেন্ট করুন এবং সেটিকে "নির্বিঘ্ন" রাখুন: একটি ফাইল যা ডিপেনডেন্সি ওয়্যার করে, বাকি কোড আচরণে ফোকাস রাখে।
ডিপেনডেন্সি ইনজেকশন (DI) মানে আপনার কোড বাইরের পক্ষ থেকে যে জিনিসগুলো প্রয়োজন সেইগুলো প্রদান করা হয়, এর বদলে কোড নিজে সেগুলো তৈরি করে না (ডাটাবেস, লগার, ঘড়ি, পেমেন্ট ক্লায়েন্ট ইত্যাদি)।
প্রায়ই এটা কনস্ট্রাকটর বা ফাংশন প্যারামিটারের মাধ্যমে ডিপেনডেন্সি পাস করা হিসেবে দেখা যায়, যাতে সেগুলো স্পষ্ট এবং বদলানো যায়।
ইনভার্সন অফ কন্ট্রোল (IoC) বড় ধারণাটি: একটি ক্লাসকে তার কাজ নিয়ে মনোনিবেশ করতে হবে, না কি সেটা কীভাবে তার সহযোগীরা পায়।
DI হলো সেই IoC অর্জনের একটি সাধারণ কৌশল—ডিপেনডেন্সি তৈরি করা বাইরের দিকে সরিয়ে নিয়ে সেগুলো ইনজেক্ট করা।
যখন ব্যবসায়িক লজিকে new দিয়ে ডিপেনডেন্সি তৈরি করা হয়, তখন সেটাকে বদলানো কঠিন হয়ে পড়ে।
ফলাফলগুলো:
DI টেস্টগুলোকে দ্রুত এবং ডিটারমিনিস্টিক রাখে কারণ আপনি প্রকৃত বহিরাগত সিস্টেমগুলোর বদলে টেস্ট ডাবল ইনজেক্ট করতে পারেন।
সাধারণ বদলগুলো:
না—DI ব্যবহার করার জন্য DI কন্টেইনার আবশ্যক নয়। ছোট বা মাঝারি অ্যাপে/manual DI দিয়ে শুরু করুন (স্পষ্টভাবে ডিপেনডেন্সি পাস করা) যেখানে:
কন্টেইনার তখন বিবেচনা করবেন যখন ওয়্যারিং পুনরাবৃত্তিমূলক বা লাইফসাইকেল ম্যানেজমেন্ট (সিঙ্গেলটন/প্রতি-রিকুয়েস্ট ইত্যাদি) দরকার হয়।
কনস্ট্রাকটর ইনজেকশন ব্যবহার করুন যখন ডিপেনডেন্সি অবজেক্টের কাজ করতে অপরিহার্য এবং অনেক মেথডে ব্যবহৃত হয়।
মেথড/প্যারামিটার ইনজেকশন ব্যবহার করুন যখন সেটা কেবল একবারের কাজের জন্য লাগে (উদাহরণ: রিকোয়েস্ট-স্কোপড ভ্যালু)।
সেটার/প্রপার্টি ইনজেকশন এড়িয়ে চলুন যতটা সম্ভব; যদি দরকার হয়, তাড়াতাড়ি ব্যর্থ হবে এমন ভ্যালিডেশন রাখুন।
কম্পোজিশন রুট হলো যেখানে আপনি অ্যাপ অ্যাসেম্বল করেন: বাস্তব ইমপ্লিমেন্টেশনগুলো তৈরি করে সেগুলোকে সার্ভিসে পাস করেন।
এটি সাধারণত অ্যাপ স্টার্টআপ (এন্ট্রি পয়েন্ট)-এর কাছে রাখুন, যাতে বাকি কোডবেস আচরণ নিয়ে ফোকাসেড থাকে, না কি ওয়্যারিং নিয়ে।
টেস্ট সীম (test seam) হবে এমন একটি জায়গা যেখানে আচরণ বদলানো যায়।
ভালো জায়গাগুলো সাধারণত কঠিন-পরীক্ষাযোগ্য বিষয়গুলোর চারপাশে:
Clock.now())DI টেস্টে এই জায়গায় বিকল্প ইমপ্লিমেন্টেশন ইনজেক্ট করে সিমুলেশন দেয়।
সাধারণ পিটফলগুলো:
container.get() ব্যবসায়িক কোডে ব্যবহার করলে ডিপেনডেন্সি অদৃশ্য হয়ে যায়—বাইকটন স্পষ্টভাবে পাস করুন।নিরাপদভাবে DI চালু করার ধাপগুলি:
প্রতিটি স্টেপ ছোট রাখুন—আপনি কোনো ধাপে থেমে থাকতে পারবেন, বড় রিরাইট ছাড়া।