হ্যালো হাসিব! আজকের লেকচারটি তোমার “Chatrabash” প্রোজেক্টের আর্কিটেকচারকে নেক্সট লেভেলে নিয়ে যাওয়ার জন্য খুবই ইম্পরট্যান্ট। আমরা শিখবো Repository Pattern সম্পর্কে, যা Clean Architecture-এর অন্যতম প্রধান ভিত্তি।
আগের লেকচারগুলোতে আমরা Service Layer-এর ভেতরেই সরাসরি DbContext ব্যবহার করে ডাটাবেস অপারেশন করেছিলাম। কিন্তু রিয়েল-ওয়ার্ল্ড এন্টারপ্রাইজ অ্যাপ্লিকেশনে এটি একটি বড় ডিজাইন ফ্ল (Design Flaw)। চলো দেখি কীভাবে রিপোজিটরি প্যাটার্ন এই সমস্যার সমাধান করে।
📝 Lecture Summary at a Glance
- The Design Flaw: Service Layer (Business Logic) এবং
DbContext(Data Access Logic) একে অপরের সাথে টাইটলি কাপলড (Tightly Coupled) হয়ে আছে। - The Solution (Repository Pattern): Business Logic এবং ডাটাবেসের মাঝে একটি অ্যাবস্ট্রাকশন লেয়ার তৈরি করা। সার্ভিস ক্লাস ডাটাবেস চেনে না, সে শুধু রিপোজিটরি ইন্টারফেসকে চেনে।
- Project Setup:
RepositoryContractsনামে নতুন একটি Class Library প্রজেক্ট তৈরি করে সেখানেICountriesRepositoryএবংIPersonsRepositoryইন্টারফেস বানানো হয়েছে। - Entity vs DTO: Repository সবসময় মূল Entity (
Person,Country) নিয়ে কাজ করে। DTO ম্যাপিংয়ের কাজ থাকে Service Layer-এ। - Dynamic Filtering: কাস্টম সার্চ বা ফিল্টারের জন্য
Expression<Func<T, bool>>ব্যবহার করা হয়েছে।
🧠 Comprehensive Breakdown & Deep Dive
১. Tightly Coupled Design Flaw (কেন রিপোজিটরি দরকার?) [Importance: 10/10]
- The Problem: ধরো তুমি “Chatrabash” প্রোজেক্টে বর্তমানে Entity Framework Core ব্যবহার করছো। ২ বছর পর তোমার টিমের মনে হলো পারফরম্যান্সের জন্য EF Core বাদ দিয়ে Dapper বা সরাসরি SQL স্টোরড প্রসিডিউর ব্যবহার করবে। যদি তোমার ডাটাবেস লজিক সার্ভিস লেয়ারের ভেতরে লেখা থাকে, তবে তোমাকে পুরো বিজনেস লজিক রিরাইট করতে হবে! এটি করতে গিয়ে অনেক নতুন বাগ তৈরি হবে (Regression Testing লাগবে)।
- The Repository Solution: রিপোজিটরি প্যাটার্নে সার্ভিস ক্লাস শুধু বলবে “আমাকে ডাটা দাও”। ডাটা SQL Server থেকে আসছে, নাকি Dapper থেকে, নাকি কোনো ডামি ইন-মেমোরি লিস্ট থেকে—সেটা সার্ভিসের জানার দরকার নেই। ডাটাবেস চেঞ্জ করতে হলে শুধু রিপোজিটরির ইমপ্লিমেন্টেশন চেঞ্জ করলেই হবে, সার্ভিস লেয়ারে একটুও হাত দিতে হবে না।
২. Creating the Repository Contracts (Interfaces) [Importance: 9/10]
- The Workflow: লেকচারার নতুন একটি প্রজেক্ট খুলেছেন এবং সেখানে শুধু ইন্টারফেসগুলো রেখেছেন। এই প্রজেক্টটি
Entitiesপ্রজেক্টের ওপর ডিপেন্ড করবে, কিন্তুServicesপ্রজেক্টের ওপর নয়। বরং উল্টোServicesপ্রজেক্ট ডিপেন্ড করবেRepositoryContracts-এর ওপর।
💻 Code Implementation (IPersonsRepository.cs):
using Entities; // Entity রেফারেন্স
using System.Linq.Expressions;
public interface IPersonsRepository
{
Task<Person> AddPerson(Person person);
Task<List<Person>> GetAllPersons();
Task<Person?> GetPersonByPersonId(Guid personId);
Task<Person> UpdatePerson(Person person);
Task<bool> DeletePersonByPersonId(Guid personId);
// ডাইনামিক ফিল্টারিংয়ের জন্য স্পেশাল মেথড
Task<List<Person>> GetFilteredPersons(Expression<Func<Person, bool>> predicate);
}
৩. The Magic of LINQ Expressions (Expression<Func<T, bool>>) [Importance: 10/10]
- The “Why”: তুমি যদি খেয়াল করো,
GetFilteredPersonsমেথডে একটি অদ্ভুত প্যারামিটার আছে:Expression<Func<Person, bool>> predicate। এটি খুবই পাওয়ারফুল একটি ফিচার। - How it works: এর মাধ্যমে তুমি Service Layer থেকে ডাটাবেসের
WHEREক্লজ ডাইনামিকভাবে রিপোজিটরিতে পাঠাতে পারবে।
💡 Example Usage (কিভাবে সার্ভিস থেকে কল করা হবে):
// যদি শুধু মেইল চেক করতে হয়:
var filteredByEmail = await _personsRepository.GetFilteredPersons(p => p.Email.Contains("gmail.com"));
// যদি নাম এবং জেন্ডার চেক করতে হয়:
var filteredByNameAndGender = await _personsRepository.GetFilteredPersons(
p => p.PersonName.Contains("Hasib") && p.Gender == "Male"
);
অর্থাৎ, রিপোজিটরির ভেতরে বারবার আলাদা মেথড (GetPersonByName, GetPersonByEmail) না লিখে, একটিমাত্র জেনেরিক ফিল্টার মেথড দিয়েই সব কাজ করে ফেলা যায়!
🚀 Modern .NET Architecture Notes & Pro Tips
১. Return Types (List vs IReadOnlyList/IEnumerable):
লেকচারার রিপোজিটরি থেকে Task<List<Person>> রিটার্ন করেছেন। ক্লিন আর্কিটেকচারে লিস্টের বদলে Task<IEnumerable<Person>> বা Task<IReadOnlyList<Person>> রিটার্ন করাটা বেস্ট প্র্যাকটিস। কারণ ডাটাবেস থেকে আসা কালেকশন সার্ভিস লেয়ারে মডিফাই (Add/Remove) করার কথা না, শুধু রিড করার কথা। IReadOnlyList দিলে এটি সিকিউর থাকে।
২. Generic Repository Pattern:
রিয়েল ওয়ার্ল্ডে প্রতিটি টেবিলের জন্য আলাদা আলাদা ইন্টারফেস (ICountriesRepository, IPersonsRepository) লেখাটা অনেক রিপিটেটিভ (Boilerplate) হয়ে যায়। বড় প্রজেক্টগুলোতে সাধারণত একটি Generic Repository তৈরি করা হয়:
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(Guid id);
Task<IReadOnlyList<T>> GetAllAsync();
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
Task<IReadOnlyList<T>> FindAsync(Expression<Func<T, bool>> predicate);
}
এরপর শুধু কাস্টম কোনো ডাটাবেস অপারেশনের দরকার হলে তখন স্পেসিফিক রিপোজিটরি বানানো হয়।
পরবর্তী লেকচারে আমরা এই ইন্টারফেসগুলোর একচুয়াল ইমপ্লিমেন্টেশন (Implementation) দেখবো, অর্থাৎ ডাটাবেস অপারেশনগুলো সার্ভিস থেকে রিপোজিটরিতে শিফট করা হবে। তুমি রেডি হলে সেই ট্রান্সক্রিপ্টটি দিতে পারো!