আমি আপনার “Simple Coding Tutor”। চলুন, আজকের লেকচারটি সহজভাবে এবং বিস্তারিতভাবে বুঝে নেওয়া যাক।
📝 Lecture Summary (Quick Revision)
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:
- Method Signature:
AddCountryমেথডটি ইনপুট হিসেবেCountryAddRequest(DTO) গ্রহণ করে এবংCountryResponse(DTO) রিটার্ন করে। - Null Validation: ইনপুট অবজেক্টটি
nullকিনা তা চেক করেArgumentNullExceptionthrow করা। - Property Validation:
CountryNameপ্রপার্টিটিnullকিনা তা চেক করেArgumentExceptionthrow করা। - Duplicate Validation: LINQ (
WhereএবংCount) ব্যবহার করে আগে থেকেই একই নামের Country আছে কিনা তা চেক করা। - DTO to Domain Model:
ToCountry()মেথড ব্যবহার করে Request DTO-কে মেইনCountry(Domain Model) অবজেক্টে কনভার্ট করা। - ID Generation:
Guid.NewGuid()কল করে নতুন Country ID তৈরি করা। - Data Source:
List<Country>(in-memory data source)-এ নতুন তৈরি করা Country অবজেক্টটি অ্যাড করা। - Domain Model to DTO:
ToCountryResponse()ব্যবহার করে Domain Model-কে Response DTO-তে কনভার্ট করে রিটার্ন করা। - Unit Testing: Visual Studio-এর Test Explorer থেকে “Run All Tests” করে ভ্যালিডেশন এবং মেথড লজিক সঠিকভাবে কাজ করছে কিনা তা ভেরিফাই করা।
🧠 Comprehensive Breakdown
এই লেকচারে আমরা মূলত CountriesService ক্লাসের ভেতরে AddCountry মেথডটির Implementation বা লজিক লেখা সম্পন্ন করেছি, যার জন্য আগের লেকচারে Unit Test লেখা হয়েছিল। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।
১. DTO এবং Domain Model Conversion (Priority: 9/10)
Concept: ক্লায়েন্ট বা Controller থেকে ডেটা সরাসরি আমাদের ডাটাবেস মডেলে (Domain Model) আসে না। এটি Data Transfer Object (DTO) এর মাধ্যমে আসে।
Why: Domain Model-কে বাইরের দুনিয়া থেকে লুকিয়ে রাখা (hide) একটি ভালো প্র্যাকটিস। Service-এর বাইরের কেউ জানবে না যে আমাদের ডাটাবেসের আসল স্ট্রাকচার কেমন। ক্লায়েন্ট শুধুমাত্র DTO পাঠাবে এবং DTO হিসেবেই রেসপন্স পাবে।
Implementation:
CountryAddRequest (DTO) থেকে Country (Domain Model)-এ ডেটা কনভার্ট করার জন্য ToCountry() নামের একটি Extension Method ব্যবহার করা হয়েছে।
// DTO কে Domain Model এ কনভার্ট করা হচ্ছে
Country country = countryAddRequest.ToCountry();
২. Data Source Initialize এবং GUID Generate (Priority: 7/10)
Concept: যেহেতু আমরা এখনো Entity Framework বা কোনো রিয়েল ডাটাবেস ব্যবহার করছি না, তাই ডেটা সংরক্ষণের জন্য একটি in-memory List<Country> তৈরি করা হয়েছে।
Why: Guid.NewGuid() ব্যবহার করে ইউনিক ID তৈরি করা হয় যাতে ডাটাবেসে প্রতিটি Country-কে আলাদাভাবে চেনা যায়। new Guid() ব্যবহার না করে Guid.NewGuid() কল করতে হয়, কারণ এটি রানটাইমে নতুন একটি ইউনিক ভ্যালু জেনারেট করে।
// ক্লাসের শুরুতে List ডিক্লেয়ার করা
private readonly List<Country> _countries;
public CountriesService()
{
_countries = new List<Country>();
}
// মেথডের ভেতরে GUID জেনারেট এবং List এ অ্যাড করা
country.CountryID = Guid.NewGuid();
_countries.Add(country);
৩. Validations এবং Exceptions (Priority: 10/10)
Concept: ইনপুট ডেটা সঠিক আছে কিনা তা নিশ্চিত করার জন্য Validation অত্যন্ত জরুরি।
Why: যদি CountryAddRequest অবজেক্টটি নিজেই null হয়, তবে আমরা ArgumentNullException থ্রো করব। আর যদি অবজেক্ট ঠিক থাকে কিন্তু এর ভেতরের প্রপার্টি CountryName null হয়, তবে আমরা ArgumentException থ্রো করব।
if (countryAddRequest == null)
{
throw new ArgumentNullException(nameof(countryAddRequest));
}
if (countryAddRequest.CountryName == null)
{
throw new ArgumentException(nameof(countryAddRequest.CountryName));
}
৪. Duplicate Data Check (LINQ) (Priority: 9/10)
Concept: ডাটাবেসে বা লিস্টে একই নামের Country দুইবার অ্যাড হওয়া ঠেকানো।
Why: ডেটার ইন্টিগ্রিটি বজায় রাখার জন্য। এখানে LINQ (Language Integrated Query)-এর Where মেথড এবং Lambda Expression ব্যবহার করা হয়েছে। temp => temp.CountryName == countryAddRequest.CountryName দিয়ে চেক করা হয় যে, লিস্টের কোনো আইটেমের নামের সাথে নতুন ইনপুট দেওয়া নামের মিল আছে কিনা। যদি Count() > 0 হয়, তার মানে এই নামটি আগে থেকেই আছে।
if (_countries.Where(temp => temp.CountryName == countryAddRequest.CountryName).Count() > 0)
{
throw new ArgumentException("Country name already exists");
}
৫. Unit Test Execution (Priority: 8/10)
Concept: কোড লেখার পর Test Explorer থেকে “Run All Tests” বাটনে ক্লিক করে চেক করা যে আমাদের লেখা লজিকগুলো আগের লেকচারে লেখা Test Case-গুলোকে Pass করতে পারছে কিনা।
- Null request test: Pass হয়েছে।
- Null country name test: Pass হয়েছে।
- Duplicate country name test: Pass হয়েছে।
- Proper country name (insertion) test: Pass হয়েছে।
💻 Complete Implementation Code (As per Transcript)
public class CountriesService : ICountriesService
{
private readonly List<Country> _countries;
public CountriesService()
{
_countries = new List<Country>();
}
public CountryResponse AddCountry(CountryAddRequest? countryAddRequest)
{
// Validation 1: Request Null Check
if (countryAddRequest == null)
{
throw new ArgumentNullException(nameof(countryAddRequest));
}
// Validation 2: Property Null Check
if (countryAddRequest.CountryName == null)
{
throw new ArgumentException(nameof(countryAddRequest.CountryName));
}
// Validation 3: Duplicate Check using LINQ
if (_countries.Where(temp => temp.CountryName == countryAddRequest.CountryName).Count() > 0)
{
throw new ArgumentException("Country name already exists");
}
// Convert DTO to Domain Model
Country country = countryAddRequest.ToCountry();
// Generate ID
country.CountryID = Guid.NewGuid();
// Add to Data Source
_countries.Add(country);
// Convert Domain Model to Response DTO and Return
return country.ToCountryResponse();
}
}
🚀 Modern approach in .NET 8 / 9 / 10 (Smarter Updates)
লেকচারে দেখানো কোডটি একেবারে বেসিক এবং কিছুটা পুরোনো (C# এর আগের ভার্সনগুলোর) স্টাইল ফলো করে। লেটেস্ট .NET (যেমন .NET 8, 9 বা 10) এবং C# 12+ ফিচার ব্যবহার করে এই কোডটিকে আরও ক্লিন ও স্মার্ট করা যায়।
কী কী পরিবর্তন করা যায়:
if (obj == null) throw new ArgumentNullException()এর বদলেArgumentNullException.ThrowIfNull()ব্যবহার করা যায়।- LINQ-এর
Where(...).Count() > 0এর বদলেAny()ব্যবহার করা অনেক বেশি পারফরম্যান্স অপ্টিমাইজড এবং রিডেবল। - C# 12 এর Primary Constructors ব্যবহার করে কনস্ট্রাক্টর লেখার কোড কমানো যায়।
Updated Modern Code:
// Primary Constructor (C# 12+) ব্যবহার করা হয়েছে
public class CountriesService() : ICountriesService
{
// Collection Expression (C# 12+) ব্যবহার করে লিস্ট ইনিশিয়ালাইজ করা
private readonly List<Country> _countries = [];
public CountryResponse AddCountry(CountryAddRequest? countryAddRequest)
{
// 1. Modern Null Check (Clean and one-liner)
ArgumentNullException.ThrowIfNull(countryAddRequest);
// 2. Exception handling with modern patterns
if (string.IsNullOrWhiteSpace(countryAddRequest.CountryName))
{
throw new ArgumentException("Country name cannot be null or empty", nameof(countryAddRequest));
}
// 3. Any() is faster than Where().Count() > 0
if (_countries.Any(temp => temp.CountryName == countryAddRequest.CountryName))
{
throw new ArgumentException("Country name already exists");
}
Country country = countryAddRequest.ToCountry();
country.CountryID = Guid.NewGuid();
_countries.Add(country);
return country.ToCountryResponse();
}
}
🏆 Best Practices (for Service & Validation Implementation)
- Always use
Any()instead ofCount() > 0: যখন আপনি শুধু জানতে চান কোনো আইটেম লিস্টে আছে কিনা, তখন LINQ এরAny()ব্যবহার করুন। এটি শর্ত পূরণ হওয়া মাত্রই খোঁজা বন্ধ করে দেয়, অন্যদিকেCount()পুরো লিস্ট স্ক্যান করে। - Throw Specific Exceptions: সাধারণ
Exceptionএর বদলে নির্দিষ্ট Exception (যেমনArgumentNullException,ArgumentException) থ্রো করুন। এটি Unit Test এবং Error Debugging-কে অনেক সহজ করে দেয়। - Keep Domain Models Private: Service লেয়ারে সব সময় Domain Model ব্যবহার করুন, কিন্তু মেথডের ইনপুট এবং রিটার্ন টাইপ হিসেবে সব সময় DTO (Data Transfer Object) ব্যবহার করুন।
- Use Modern Built-in Helpers:
nullচেকিং এর জন্য সব সময়ArgumentNullException.ThrowIfNull()ব্যবহার করুন (যদি আপনি .NET 6 বা তার উপরের ভার্সন ব্যবহার করেন)।