إنشاء Basket Repository للتعامل مع Redis
المشكلة الأساسية
بما إن الـ Generic Repository بيتعامل مع الـ StoreContext
الخاص بـ Entity Framework، ومش هيقدر يتعامل مع الـ Redis، فإحنا محتاجين نعمل Repository مخصص للتعامل مع الـ Customer Basket في Redis.
1. تعريف واجهة IBasketRepository
هنبدأ بإنشاء Interface خاصة بالـ Basket Repository داخل مشروع Core في فولدر Repositories.Contract
:
الكود:
public interface IBasketRepository
{
Task<CustomerBasket?> GetBasketAsync(string basketId);
// Retrieve basket by Id
Task<CustomerBasket?> UpdateBasketAsync(CustomerBasket basket);
// Create or Update basket
Task<bool> DeleteBasketAsync(string basketId);
// Delete basket by Id
}
2. إنشاء BasketRepository
هنقوم بعمل Implement للـ IBasketRepository
في Class جديد في الـ Repository.
الكود:
public class BasketRepository : IBasketRepository
{
private readonly IDatabase _database;
public BasketRepository(IConnectionMultiplexer redis)
{
_database = redis.GetDatabase();
// إنشاء الـ Database Object من Redis Connection
}
public async Task<CustomerBasket?> GetBasketAsync(string basketId)
{
var basket = await _database.StringGetAsync(basketId);
// قراءة السلة من Redis
return basket.IsNullOrEmpty ? null : JsonSerializer.Deserialize<CustomerBasket>(basket); // Deserialize
}
public async Task<CustomerBasket?> UpdateBasketAsync(CustomerBasket basket)
{
var createdOrUpdated = await _database.StringSetAsync(
basket.Id,
JsonSerializer.Serialize(basket),
TimeSpan.FromDays(30) // تحديد مدة الصلاحية
);
if (!createdOrUpdated) return null; // لو حصل خطأ في الحفظ
return await GetBasketAsync(basket.Id); // إعادة السلة بعد التحديث
}
public async Task<bool> DeleteBasketAsync(string basketId)
{
return await _database.KeyDeleteAsync(basketId);
// حذف السلة بناءً على الـ Id
}
}
3. إضافة Redis Connection في المشروع
تنزيل Package Redis
هنستخدم الباكدج StackExchange.Redis
لأنها موثوقة وسهلة الاستخدام:
dotnet add package StackExchange.Redis
- أنا محتاج الأول أعمل Install للـ Redis package وهتبقا في الـ Project Repository
- بس خد بالك أنا هحتاج أستخدمها تاني في الـ Cashing ودي هتبقا في الـ Service فأنا بدل ما أعمل Implement في الـ Repository وبعدين أعمل Implement في الـ Service
- فأنا هعمل Implement في الـ Core على طول ومنه هاخد Reference للـ Repository والـ Service
- فاحنا هنضيف الـ Package في الـ Core واسمها
StackExchange.Redis
إعداد الـ Dependency Injection
هنقوم بتسجيل الـ IConnectionMultiplexer
في الـ DI Container باستخدام Singleton:
الكود:
// في ملف Program.cs
builder.Services.AddSingleton<IConnectionMultiplexer>(serviceProvider =>
{
var connection = builder.Configuration.GetConnectionString("Redis");
// قراءة الـ Connection String من الـ appsettings
return ConnectionMultiplexer.Connect(connection);
// إنشاء اتصال بالـ Redis
});
// إضافة الـ Connection String في appsettings.json
// "Redis": "localhost"
ليه Singleton؟
- الـ Redis Connection يحتاج يفضل مفتوح طوال فترة تشغيل التطبيق لتجنب إعادة إنشاء الاتصال في كل مرة يتم استدعاء Repository.
- لو كان Scoped، كل طلب جديد هيعمل Connection جديد، وده بيؤدي لإهدار الموارد.
- الـSingleton بيضمن إن الاتصال ثابت ومتاح طوال الوقت.
4. التعامل مع Redis باستخدام IDatabase
4.1. قراءة البيانات:
- نستخدم
StringGetAsync
لقراءة السلة بناءً على الـbasketId
. - يتم فحص إذا كانت القيمة فارغة أو Null، ولو مش فارغة يتم تحويلها إلى Object باستخدام
JsonSerializer
.
4.2. إنشاء أو تحديث البيانات:
- نستخدم
StringSetAsync
لتخزين السلة كـ JSON في Redis. - نحدد مدة الصلاحية (TTL) للسلة، مثلًا 30 يوم.
4.3. حذف البيانات:
- يتم حذف السلة باستخدام
KeyDeleteAsync
.
الكود النهائي لـ BasketRepository
:
public class BasketRepository : IBasketRepository
{
private readonly IDatabase _database;
public BasketRepository(IConnectionMultiplexer redis)
{
_database = redis.GetDatabase();
}
public async Task<CustomerBasket?> GetBasketAsync(string basketId)
{
var basket = await _database.StringGetAsync(basketId);
return basket.IsNullOrEmpty ? null : JsonSerializer.Deserialize<CustomerBasket>(basket);
}
public async Task<CustomerBasket?> UpdateBasketAsync(CustomerBasket basket)
{
var createdOrUpdated = await _database.StringSetAsync(
basket.Id,
JsonSerializer.Serialize(basket),
TimeSpan.FromDays(30)
);
if (!createdOrUpdated) return null;
return await GetBasketAsync(basket.Id);
}
public async Task<bool> DeleteBasketAsync(string basketId)
{
return await _database.KeyDeleteAsync(basketId);
}
}
الخلاصة:
- أنشأنا واجهة
IBasketRepository
لتعريف العمليات الأساسية:GetBasketAsync
UpdateBasketAsync
DeleteBasketAsync
- قمنا بتنفيذ الواجهة في
BasketRepository
باستخدام Redis. - أضفنا الـ Redis Connection كـ Singleton في الـ Dependency Injection.
- استخدمنا مكتبة
StackExchange.Redis
للتعامل مع Redis بشكل مباشر. - التصميم يدعم التوسع لإضافة مزايا مثل الـ Caching أو تحسين الأداء مستقبلًا.