হ্যালো হাসিব! তুমি এখন Section 12: Dependency Injection-এর দ্বাদশ লেকচার “Best Practices for DI” (Lecture 139)-এ আছো।

এতক্ষণ আমরা Dependency Injection (DI)-এর মেকানিজম শিখেছি। এই লেকচারটি ইন্টারভিউ এবং রিয়েল-ওয়ার্ল্ড স্কেলেবল প্রোজেক্টের জন্য খুবই গুরুত্বপূর্ণ, কারণ এখানে লেকচারার DI ব্যবহার করার সময় কী করা উচিত (Best Practices) এবং কী করা উচিত নয় (Anti-patterns/Bad Practices) তা নিয়ে বিস্তারিত আলোচনা করেছেন।

চলো, পয়েন্টগুলো খুব সহজে ডিকোড করে ফেলি।


সারসংক্ষেপ (Quick Revision List)

  • State in Singleton: Singleton সার্ভিসে গ্লোবাল স্টেট (ডেটা) রাখলে List বা Dictionary-এর বদলে ConcurrentBag বা ConcurrentDictionary ব্যবহার করতে হবে (Thread safety-এর জন্য)।
  • Avoid Service Locator Pattern: Child Scope ছাড়া সরাসরি GetRequiredService() বা GetService() কল করে ম্যানুয়ালি সার্ভিস রিসিভ করা উচিত নয়। সবসময় Constructor Injection ব্যবহার করবে।
  • Don’t Dispose Manually: ইনজেক্ট হওয়া সার্ভিসের Dispose() মেথড কখনোই নিজে থেকে কল করবে না। IoC Container নিজে থেকেই স্কোপ শেষে সেগুলো ডিসপোজ করে দেবে।
  • Captive Dependency (100% Interview Question): কখনোই একটি Transient বা Scoped সার্ভিসকে একটি Singleton সার্ভিসের ভেতরে ইনজেক্ট করবে না। কারণ Singleton আজীবন বাঁচে, তার পেটে অন্যগুলো ঢুকে গেলে তারাও আজীবন বেঁচে থাকবে এবং মেমোরি লিক হবে।
  • Don’t Share Service Instances: একটি ক্লাসের ইনজেক্ট হওয়া সার্ভিসকে public করে অন্য ক্লাস থেকে অ্যাক্সেস করার চেষ্টা করবে না। যার যে সার্ভিস লাগবে, সে তার নিজের কনস্ট্রাক্টরে সেটি ইনজেক্ট করে নেবে।

Comprehensive Breakdown

১. Global State in Singleton (Concurrency Issues) [Priority: 10/10]

Singleton সার্ভিস পুরো অ্যাপ্লিকেশনে একটিমাত্র অবজেক্ট হিসেবে থাকে। যদি এই সার্ভিসে তুমি কোনো লিস্টে ডাটা সেভ করো, তখন একই সাথে ১০০ জন ইউজার যদি রিকোয়েস্ট পাঠায় (100 Threads), তবে সবাই ওই একই লিস্টে ডাটা রিড/রাইট করার চেষ্টা করবে। সাধারণ List<T> বা Dictionary<K,V> এই মাল্টি-থ্রেডিং সাপোর্ট করে না, ফলে অ্যাপ্লিকেশন ক্র্যাশ করতে পারে। Best Practice:

  • ছোট ডাটার জন্য System.Collections.Concurrent নেমস্পেস থেকে ConcurrentBag<T> বা ConcurrentDictionary<K,V> ব্যবহার করবে। এগুলো থ্রেড-সেফ (Thread-safe)।
  • বড় ডাটার জন্য Singleton-এর বদলে Distributed Cache (যেমন: Redis) ব্যবহার করবে।

২. Request State in Services (ডাটা শেয়ারিং) [Priority: 7/10]

একই রিকোয়েস্টের ভেতরে (যেমন Controller থেকে View বা Middleware থেকে Controller) ডাটা শেয়ার করার জন্য Scoped সার্ভিসে ভ্যারিয়েবল বানিয়ে তাতে ডাটা রাখা উচিত নয়। Best Practice: একই রিকোয়েস্টের মাঝে ডাটা পাস করার জন্য ASP.NET Core-এর HttpContext.Items ডিকশনারি ব্যবহার করা সবচেয়ে পারফেক্ট উপায়।

৩. Avoid Service Locator Pattern [Priority: 10/10]

মাঝে মাঝে ডেভেলপাররা Constructor-এ ইনজেক্ট না করে, সরাসরি অ্যাকশন মেথডের ভেতরে HttpContext.RequestServices.GetRequiredService<T>() লিখে সার্ভিস বের করে নেয়। একে Service Locator Pattern বলে। এটি একটি এন্টি-প্যাটার্ন বা Bad Practice। Why? কারণ কোড দেখলে বোঝা যায় না যে এই ক্লাসটি কোন কোন সার্ভিসের উপর নির্ভরশীল (Hidden Dependencies)। Exception: তুমি যদি ম্যানুয়ালি কোনো Child Scope তৈরি করো (আগের লেকচারের মতো), শুধুমাত্র তখনই GetRequiredService ব্যবহার করতে পারবে। অন্যথায় সবসময় Constructor Injection ব্যবহার করবে।

(নোট: GetRequiredService সার্ভিস খুঁজে না পেলে Error থ্রো করে, আর GetService সার্ভিস না পেলে null রিটার্ন করে।)

৪. Don’t Dispose Manually (নিজে ডিসপোজ করবে না) [Priority: 9/10]

IoC Container যে সার্ভিসগুলো তৈরি করে, সেগুলো ডিসপোজ করার দায়িত্বও তার নিজের (End of Scope-এ)। Bad Practice: তুমি যদি কোনো ইনজেক্ট হওয়া সার্ভিসকে using ব্লকের ভেতর রাখো অথবা নিজে থেকে _service.Dispose() কল করে দাও, তাহলে অ্যাপ্লিকেশন ক্র্যাশ করতে পারে।

৫. Captive Dependency (The Most Important Warning) [Priority: 10/10]

এটি DI-এর সবচেয়ে ডেঞ্জারাস বাগ (Bug) এবং ইন্টারভিউয়ের ফেভারিট প্রশ্ন। The Problem: তুমি যদি একটি Transient (যে রিকোয়েস্ট শেষে মরে যাওয়ার কথা) সার্ভিসকে একটি Singleton সার্ভিসের কনস্ট্রাক্টরে ইনজেক্ট করো, তাহলে কী হবে? Singleton তো আর মরবে না, তাই সে তার পেটের ভেতর ঐ Transient সার্ভিসটিকেও আজীবন বাঁচিয়ে রাখবে! এর ফলে Transient সার্ভিসটি আসলে একটি Singleton-এর মতো আচরণ করবে। একেই Captive Dependency বলে, যার ফলে ভয়াবহ মেমোরি লিক (Memory Leak) হয়।

The Golden Rule Chart:

  • Transient Service: এর ভেতরে Transient, Scoped, Singleton—সবকিছুই ইনজেক্ট করা যাবে।
  • Scoped Service: এর ভেতরে Transient, Scoped, Singleton—সবকিছুই ইনজেক্ট করা যাবে।
  • Singleton Service: এর ভেতরে শুধুমাত্র Singleton ইনজেক্ট করা যাবে। (কখনোই Scoped বা Transient দেওয়া যাবে না)।

৬. Do not store service reference to public fields [Priority: 8/10]

ধরো HomeController-এ একটি সার্ভিস ইনজেক্ট হলো। তুমি সেটিকে public _citiesService বানিয়ে দিলে, যাতে অন্য কোনো ক্লাস সরাসরি HomeController.citiesService কল করে ডাটা পেতে পারে। এটি জঘন্য একটি প্র্যাকটিস। Why? কারণ যখন অন্য ক্লাস ঐ ফিল্ড অ্যাক্সেস করতে যাবে, ততক্ষণে হয়তো HomeController-এর রিকোয়েস্ট শেষ হয়ে স্কোপ ডেস্ট্রয় হয়ে গেছে! ফলে ObjectDisposedException খাবে। Best Practice: যার যে সার্ভিস লাগবে, সে তার নিজের কনস্ট্রাক্টরে সেটি ইনজেক্ট করে নেবে।


Best Practices & .NET 10 Context

.NET 10 Context: ASP.NET Core 10-এ এই নিয়মগুলো ১০০% সেম। তবে .NET 10-এ ফ্রেমওয়ার্ক নিজে থেকেই অনেক বেশি প্রোটেক্টিভ হয়ে গেছে। তুমি যদি ডেভেলপমেন্ট এনভায়রনমেন্টে (Development Environment) ভুল করে Singleton-এর ভেতর Scoped ইনজেক্ট করে ফেলো, তাহলে .NET 10 অ্যাপ স্টার্ট হওয়ার সময়ই তোমাকে InvalidOperationException থ্রো করে আটকে দেবে (Validate scopes at startup)! ফলে প্রোডাকশনে যাওয়ার আগেই তুমি এই বাগটি ধরতে পারবে।

Example of .NET 10 Safety:

// Program.cs
var builder = WebApplication.CreateBuilder(args);
 
// .NET 10 will throw exception at startup if Captive Dependency exists
builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = true; 
    options.ValidateOnBuild = true;
});
 

হাসিব, এই রুলসগুলো কি তোমার কাছে ক্লিয়ার হয়েছে? রেডি থাকলে আমরা এই সেকশনের একদম শেষ লেকচার “Autofac”-এ মুভ করতে পারি, যেখানে আমরা শিখবো ডিফল্ট IoC Container-এর বদলে থার্ড-পার্টি Container কীভাবে ব্যবহার করতে হয়!