بناء نظام حجز تذاكر حافلات (Bus Transit) متزامن بدون أخطاء في لارافيل (Laravel)
تخيل معايا السيناريو ده: عندك تطبيق لحجز تذاكر الأوتوبيس، وفيه كرسي واحد بس فاضي، وفجأة اتنين مستخدمين دخلوا يحجزوه في نفس اللحظة بالظبط. لو الكود بتاعك مش متظبط، السيستم ممكن يسمح للاتنين يحجزوا نفس الكرسي، وهنا تبدأ المشاكل مع العملاء وتخسر ثقتهم. ده اللي بنسميه في عالم البرمجة مشكلة الـ Race Condition، والنهاردة هنتعلم إزاي نحلها في لارافيل (Laravel) باحترافية.
Table of contents [Show]
ليه مشكلة التزامن (Concurrency) صعبة؟
في العادة، الكود بتاعك بيمشي خطوتين: أولاً بيتأكد إن الكرسي متاح، وبعدين بيحجز الكرسي. المشكلة إن لو فيه طلبين دخلوا في نفس الميكرو-ثانية، الاتنين هيشوفوا إن الكرسي متاح قبل ما أي واحد فيهم يسجل الحجز، والنتيجة إن الاتنين هياخدوا "تأكيد حجز" لنفس المقعد. الحل مش في كود عادي، الحل في حاجة اسمها قفل قاعدة البيانات (Database Locking).
استخدام القفل التشاؤمي (Pessimistic Locking)
في لارافيل، عندنا ميزة عبقرية اسمها lockForUpdate. الفكرة إنك بتقول لقاعدة البيانات: "يا جماعة، أنا بقرأ بيانات الكرسي ده وعاوز أحجزه، فمحدش يلمس الصف ده لحد ما أخلص شغلي".
// كود الحجز الآمن
DB::transaction(function () use ($seatId, $userId) {
$seat = Seat::where('id', $seatId)
->lockForUpdate()
->first();
if ($seat->is_booked) {
throw new \Exception("الكرسي محجوز بالفعل!");
}
$seat->update(['is_booked' => true, 'user_id' => $userId]);
});
السطر lockForUpdate هو اللي بيعمل "سحر" الـ Transaction، لأنه بيخلي أي طلب تاني يحاول يقرأ نفس الصف يستنى لحد ما الترانزكشن الأولاني يخلص.
استخدام القفل التفاؤلي (Optimistic Locking)
لو نظامك عليه ضغط هائل جداً، ممكن lockForUpdate يعمل بطء (Bottleneck). هنا بنلجأ للقفل التفاؤلي، وده بيعتمد على وجود عمود في الجدول اسمه version. الفكرة إنك بتحدث الصف بشرط إن رقم النسخة هو اللي قرأته في الأول.
$affected = Seat::where('id', $seatId)
->where('version', $currentVersion)
->update([
'is_booked' => true,
'version' => $currentVersion + 1
]);
if (!$affected) {
throw new \Exception("حدث خطأ، حاول مرة أخرى.");
}
خطوات تنفيذ النظام باحترافية
- تصميم قاعدة البيانات (Database Schema): تأكد إن جدول المقاعد فيه عمود للـ
statusأوis_booked، ولو هتستخدم القفل التفاؤلي ضيف عمودversion. - التحقق (Validation): استخدم
FormRequestللتأكد إن بيانات الحجز سليمة قبل ما تلمس قاعدة البيانات. - التعامل مع الاستثناءات (Exception Handling): لازم يكون عندك رسائل خطأ واضحة للمستخدم في حالة فشل الحجز بسبب التزامن.
- طوابير الانتظار (Queue Jobs): في الأنظمة الكبيرة جداً، يفضل الحجز يتم من خلال
Queueعشان تضمن معالجة الطلبات بالترتيب.
نصيحة من أخ لمبرمج زيك
يا صديقي، مهارة التعامل مع الـ Data Integrity أو سلامة البيانات هي اللي بتفرق المبرمج المبتدئ عن المحترف. دايماً فكر في "أسوأ سيناريو" ممكن يحصل في تطبيقك (Worst Case Scenario) وابني كودك على أساسه. لا تعتمد أبداً على إن كل شيء هيكون مثالي؛ قاعدة البيانات هي الحصن الأخير، اتعلم إزاي تخليها تحمي بياناتك من أي تضارب. ابدأ طبق الكلام ده في مشاريعك الجاية وهتلاقي جودة شغلك اتنقلت لمستوى تاني خالص.