আমি আপনার এক্সপার্ট সফটওয়্যার ইঞ্জিনিয়ারিং ট্রেইনার। কোর্স আউটলাইন অনুযায়ী, আমরা এখন Section 15: xUnit testing-এর ভেতরে আছি। এই লেকচারটিতে আমরা UpdatePerson মেথডের ইউনিট টেস্ট লেখার প্রস্তুতির অংশ হিসেবে একটি নতুন DTO (Data Transfer Object) তৈরি করা শিখেছি।
চলুন, আজকের লেকচারটি গুছিয়ে ও বিস্তারিতভাবে বুঝে নেওয়া যাক।
📝 Lecture Summary (Quick Revision)
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:
- The Goal: বিদ্যমান কোনো Person-এর ডেটা আপডেট করার জন্য
UpdatePersonফাংশনালিটির কাজ শুরু করা। - New DTO (
PersonUpdateRequest):ServiceContracts/DTOফোল্ডারেPersonUpdateRequestনামে একটি নতুন ক্লাস তৈরি করা। - Copying Properties:
PersonAddRequestক্লাসের সব প্রপার্টি এবং Data Annotations কপি করে নতুন এই DTO-তে পেস্ট করা। - Adding
PersonID: আপডেটের ক্ষেত্রে কোন ডেটাটি আপডেট হবে তা জানার জন্যPersonID(Guid) প্রপার্টিটি ম্যান্ডেটরি বা[Required]হিসেবে যোগ করা। - Updating
ToPersonMethod:PersonUpdateRequest-এর ডেটা থেকেPersonমডেলে কনভার্ট করার মেথডটিতেPersonIDকপি করার লজিক অ্যাড করা। - Reverse Mapping in
PersonResponse:PersonResponseক্লাসেToPersonUpdateRequest()নামে একটি মেথড তৈরি করা, যা টেস্টিংয়ের সময় রেসপন্স ডেটাকে আবার আপডেট রিকোয়েস্টে কনভার্ট করতে সাহায্য করবে। - Enum Conversion: স্ট্রিং থেকে Enum-এ ডেটা কনভার্ট করার জন্য
Enum.Parseএর ব্যবহার শেখা।
🧠 Comprehensive Breakdown
এই লেকচারে আমরা আপডেট অপারেশনের জন্য প্রয়োজনীয় আর্কিটেকচারাল সেটআপ (DTOs) তৈরি করেছি। চলুন প্রতিটি ধাপ বিস্তারিতভাবে বুঝে নিই।
১. PersonUpdateRequest DTO তৈরি করা (Priority: 10/10)
Concept & Why: যখন আমরা কোনো নতুন ইউজার তৈরি করি, তখন আমরা তার কাছ থেকে ID চাই না (ID অটোমেটিক জেনারেট হয়)। তাই PersonAddRequest ক্লাসে PersonId ছিল না। কিন্তু যখন কোনো ইউজারকে আপডেট করতে হবে, তখন কন্ট্রোলারকে অবশ্যই বলে দিতে হবে সে কোন ইউজারকে আপডেট করতে চায়।
এজন্য PersonUpdateRequest নামে একটি নতুন DTO তৈরি করতে হয়, যেখানে PersonId ম্যান্ডেটরি বা [Required]। বাকি সব ফিল্ড (Name, Email, Age) PersonAddRequest-এর মতোই থাকে।
// ServiceContracts/DTO/PersonUpdateRequest.cs
using System.ComponentModel.DataAnnotations;
public class PersonUpdateRequest
{
// আপডেটের জন্য PersonId থাকা বাধ্যতামূলক
[Required(ErrorMessage = "Person ID can't be blank")]
public Guid PersonID { get; set; }
[Required(ErrorMessage = "Person Name can't be blank")]
public string? PersonName { get; set; }
[Required(ErrorMessage = "Email can't be blank")]
[EmailAddress(ErrorMessage = "Email should be a valid email address")]
public string? Email { get; set; }
// ... বাকি সব প্রপার্টিগুলো AddRequest এর মতোই থাকবে
}
২. ToPerson() মেথড আপডেট করা (Priority: 9/10)
Concept & Why: PersonUpdateRequest ক্লাসের ভেতরেও ToPerson() মেথড থাকতে হবে, যাতে DTO থেকে ডাটাবেস মডেলে কনভার্ট করা যায়। পার্থক্য হলো, এবার আমাদেরকে PersonID প্রপার্টিটিও কপি করে মডেলে অ্যাসাইন করতে হবে।
public Person ToPerson()
{
return new Person()
{
PersonID = PersonID, // এই ফিল্ডটি AddRequest এ ছিল না
PersonName = PersonName,
Email = Email,
// ... অন্যান্য প্রপার্টি
};
}
৩. Reverse Mapping: ToPersonUpdateRequest() (Priority: 8/10)
Concept & Why: যখন আমরা Unit Test লিখব, তখন আমরা ডাটাবেস থেকে একটি Person তুলব (যা PersonResponse হিসেবে আসবে), তারপর তার নাম বা ইমেইল চেঞ্জ করে আবার ডাটাবেসে সেভ (Update) করতে পাঠাব। কিন্তু Update মেথডটি ইনপুট হিসেবে PersonUpdateRequest গ্রহণ করে।
তাই PersonResponse ক্লাসের ভেতরে এমন একটি মেথড থাকতে হবে, যা নিজেকে PersonUpdateRequest-এ কনভার্ট করতে পারে।
// DTO/PersonResponse.cs ক্লাসের ভেতরে
public PersonUpdateRequest ToPersonUpdateRequest()
{
return new PersonUpdateRequest()
{
PersonID = PersonID,
PersonName = PersonName,
Email = Email,
DateOfBirth = DateOfBirth,
// ... অন্যান্য প্রপার্টি
// Gender Conversion (String to Enum) নিচে ব্যাখ্যা করা হলো
};
}
৪. String কে Enum এ কনভার্ট করা (Priority: 10/10)
Concept & Why: ওপরে যে ম্যাপিংটি করা হচ্ছে, সেখানে একটি চ্যালেঞ্জ আছে। PersonResponse ক্লাসে Gender হলো একটি string (যেমন: "Male"), কিন্তু PersonUpdateRequest ক্লাসে Gender হলো একটি enum (GenderOptions.Male)।
C#-এ স্ট্রিংকে Enum-এ কনভার্ট করার জন্য Enum.Parse() মেথড ব্যবহার করা হয়।
// Enum.Parse এর ৩টি প্যারামিটার:
// ১. Enum এর Type, ২. স্ট্রিং ভ্যালু, ৩. Case-Insensitive হবে কিনা (true)
Gender = (GenderOptions)Enum.Parse(typeof(GenderOptions), Gender, true)
(নোট: এই কনভার্সনটি শুধুমাত্র তখনই কাজ করবে যখন Gender স্ট্রিংটি null না হয়। রিয়েল প্রোজেক্টে null চেক করে নেওয়া ভালো)।
🚀 Modern C# (.NET 10 Update) & Smarter Approach
লেকচারের ম্যানুয়াল ম্যাপিং পদ্ধতিটি কাজ করলেও, আধুনিক এন্টারপ্রাইজ প্রোজেক্টে এগুলোর আরও স্মার্ট সমাধান আছে।
1. Enum.Parse এর আধুনিক বিকল্প (Enum.TryParse):
Enum.Parse ব্যবহার করলে যদি স্ট্রিং ভ্যালুটি (যেমন “UnknownGender”) Enum-এ না থাকে, তবে অ্যাপ্লিকেশন ক্র্যাশ করে (Exception থ্রো হয়)। আধুনিক C#-এ Enum.TryParse() ব্যবহার করা অনেক বেশি নিরাপদ।
// Safe Enum parsing
GenderOptions? parsedGender = Enum.TryParse<GenderOptions>(Gender, true, out var result) ? result : null;
2. Avoid Manual Reverse Mapping (Use AutoMapper):
লেকচারার PersonResponse কে PersonUpdateRequest এ কনভার্ট করার জন্য নিজে হাতে প্রপার্টি ধরে ধরে কোড লিখেছেন। কিন্তু রিয়েল-ওয়ার্ল্ড প্রোজেক্টে আমরা AutoMapper ব্যবহার করি, যা এই কাজটিকে জাস্ট এক লাইনে নামিয়ে আনে:
// In Test File using AutoMapper
var updateRequest = _mapper.Map<PersonUpdateRequest>(personResponse);
// No need to write 20 lines of manual mapping code!
3. Composition over Duplication (Advanced DTO Design):
লেকচারে PersonAddRequest এর সব কোড কপি করে PersonUpdateRequest-এ পেস্ট করা হয়েছে। এর ফলে DRY (Don’t Repeat Yourself) রুল ব্রেক হয়েছে। কাল যদি আপনি নতুন একটি ফিল্ড (যেমন: PhoneNumber) যোগ করতে চান, তবে আপনাকে দুটি ক্লাসেই গিয়ে ম্যানুয়ালি যোগ করতে হবে।
আধুনিক C# আর্কিটেকচারে ہمরা একটি Base DTO তৈরি করি, এবং বাকিগুলো তা থেকে ইনহেরিট করে।
Smarter DTO Design:
// 1. Base Class (Shared properties)
public abstract class PersonRequestBase
{
public string? PersonName { get; set; }
public string? Email { get; set; }
// ... other shared properties
}
// 2. Add Request
public class PersonAddRequest : PersonRequestBase { }
// 3. Update Request
public class PersonUpdateRequest : PersonRequestBase
{
[Required]
public Guid PersonID { get; set; }
}
🏆 Best Practices (For DTOs)
- Keep Models Clean: লেকচারে যেমন Update এর জন্য আলাদা DTO বানানো হয়েছে, এটি চমৎকার একটি প্র্যাকটিস। কখনোই একই DTO দিয়ে Insert এবং Update এর কাজ চালানোর চেষ্টা করবেন না, কারণ তাদের ভ্যালিডেশন রুলস আলাদা হয় (যেমন আপডেটে ID ম্যান্ডেটরি)।
- Safe Parsing: স্ট্রিং থেকে Enum বা ডেট-টাইম পার্স করার সময় সবসময়
TryParseব্যবহার করবেন, যাতে ভুল ইনপুটে অ্যাপ্লিকেশন ক্র্যাশ না করে। - Use Mapster or AutoMapper: ম্যানুয়ালি
ToPerson()বাToPersonUpdateRequest()লেখার অভ্যাসটি লার্নিংয়ের জন্য ভালো, কিন্তু প্রোজেক্ট বড় হলে মেইনটেইন করা অসম্ভব হয়ে দাঁড়ায়। তাই Mapping লাইব্রেরি ব্যবহার করা শেখা উচিত।