يا جماعة الخير، السلام عليكم. معكم أخوكم أبو عمر.
خلوني أحكيلكم هالشغلة اللي صارت معي زمان، أيام ما كنا في غزة بنبني أنظمة تحت ظروف… يعلم بها الله. ما كان عنا رفاهية السيرفرات الكبيرة ولا الـ “Hyper Scale” ولا كل هالمصطلحات الرنانة. كان عنا API بسيط، حركة مرور متقلبة، وبنية تحتية مضغوطة على آخرها. السؤال ما كان “شو الأفضل نظريًا؟”، السؤال كان دايماً: “شو اللي راح يصمد لما تشتد الأمور؟”.
كان معنا مطور شب، ما شاء الله عليه، شعلة ذكاء. لاحظت إنه بحط الكاش في كل Controller تقريباً. لما سألته ليش، جاوبني بكل ثقة: “يا أبو عمر، أقرب نقطة للـ response… يعني أسرع إشي”.
مرت السنين… وهداك الشب اليوم صار من كبار المبرمجين في Google لمنطقة الشرق الأوسط. لكن ما وصل لهناك إلا لما تعلم إن جوابه هداك… كان ناقص كتير.
اليوم، بدي أحكيلكم عن هالسؤال اللي بظاهره بسيط، لكنه بيكشف عقلية المهندس الحقيقية.
السؤال الذي يفرز المهندسين عن المطورين
في أي مقابلة عمل متقدمة في Laravel، ممكن ينسألك هالسؤال:
❓ أين تضع منطق التخزين المؤقت (Cache) في تطبيقك: في الـ Controller؟ أم الـ Service Layer؟ أم الـ Repository؟ ولماذا؟
هذا مش مجرد سؤال تقني، هاد “سؤال فخ” (Trap Question). الإجابة عليه بتكشف طريقة تفكيرك، وهل بتفكر كمبرمج بيكتب كود، ولا كمهندس بيبني نظام.
الإجابة “الآمنة” (لكنها ليست الأفضل)
أغلب المطورين الشاطرين راح يجاوبوا:
“أضعه في طبقة الخدمات (Service Layer). هذا يساعد على تقليل تكرار الكود، وفصل منطق العمل (Business Logic) عن طبقة العرض (Controller)، ويجعل الكود أنظف.”
وهذا جواب جيد، صحيح، وما عليه غبار. لكنه جواب “آمن”. ما بيميزك عن غيرك. هو بيحكي للمُحاوِر إنك بتعرف مبادئ التصميم النظيف، لكنه ما بيحكي إشي عن فهمك العميق للأنظمة.
الإجابة التي تكشف العقلية الفذّة 🧠
المهندس اللي عنده نظرة أوسع راح يجاوب بطريقة مختلفة. راح يبدأ بتفكيك السؤال نفسه:
“لحظة، الكاش مش مفهوم واحد. السؤال لازم يكون أدق. هل تقصد تخزين نتيجة استعلام (Query Cache)؟ أم تخزين كائن نقل بيانات (DTO Cache)؟ أم تخزين الاستجابة النهائية (Response Cache)؟ لكل نوع مكان مناسب.”
هنا المُحاوِر بيبدأ يصحصح. ثم يكمل المهندس:
“القاعدة العامة عندي هي: أضع الكاش في أقرب نقطة لثبات البيانات، وأبعد نقطة عن عملية تسليمها (Delivery). وأحيانًا، أفضل مكان للكاش مش جوا Laravel أصلاً، ممكن يكون على مستوى البنية التحتية مثل CDN أو Reverse Proxy.”
هذه مش إجابة مطور، هذه إجابة مهندس أنظمة. خلينا نفصلها عمليًا.
مثال Laravel واقعي: أين يظهر الفرق؟
لنفترض عنا صفحة بتعرض آخر المقالات في الموقع.
❌ الطريقة الخاطئة (والشائعة): الكاش في الـ Controller
هاي طريقة الشب الذكي اللي حكيتلكم عنه في البداية. الكود بكون شكله هيك:
// ArticleController.php
class ArticleController extends Controller
{
public function index()
{
// تذكر لي نتيجة هذا الكود لمدة 10 دقائق
return Cache::remember('articles.index.page', 600, function () {
// جيب آخر المقالات ورجعها كـ JSON
return ArticleResource::collection(Article::latest()->get());
});
}
}
🔴 ليش هاي الطريقة سيئة؟
- هشاشة (Brittle): الكاش صار مرتبط ارتباط وثيق بطريقة العرض (delivery). لو غيرت أي إشي في شكل الـ `ArticleResource` (مثلاً أضفت حقل جديد)، لازم تكسر الكاش يدويًا. لو احتجت ترجع البيانات بصيغة مختلفة لمستخدم عنده صلاحيات مختلفة، الكاش هاد ما بنفعك.
- صعوبة إعادة الاستخدام: ماذا لو احتجت نفس قائمة المقالات في مكان آخر؟ مثلاً في `API endpoint` ثاني، أو في `background job`، أو في `console command`؟ راح تضطر تكرر نفس منطق الكاش أو تتجاهله تمامًا.
- صعوبة الاختبار والتتبع: اختبار هذا الـ Controller صار أصعب لأنه بيعتمد على حالة خارجية (الكاش). تتبع الأخطاء المتعلقة بالكاش بصير كابوس.
باختصار، هذا كاش سريع… لكنه سريع الانكسار.
✅ الحل الأفضل: الكاش عند نقطة ثبات البيانات (Repository)
الحل الأفضل هو إنك تنزل بالكاش لطبقة أعمق، طبقة مسؤولة فقط عن جلب البيانات من مصدرها. هذه الطبقة عادةً ما تكون الـ Repository Pattern.
1. إنشاء الـ Repository وتضمين الكاش
// app/Repositories/ArticleRepository.php
class ArticleRepository
{
public function getLatestArticles()
{
// الكاش هنا مرتبط بالبيانات الخام، مش بطريقة عرضها
return Cache::remember(
'articles.latest.raw', // مفتاح كاش واضح
now()->addMinutes(10),
fn () => Article::latest()->get()
);
}
}
2. استخدام الـ Repository في الـ Service والـ Controller
// app/Services/ArticleService.php
class ArticleService
{
public function __construct(private ArticleRepository $repo) {}
public function fetchLatestArticlesForHomepage()
{
// هنا ممكن تضيف أي Business Logic قبل أو بعد جلب البيانات
$articles = $this->repo->getLatestArticles();
// ... أي منطق إضافي
return $articles;
}
}
// ArticleController.php
class ArticleController extends Controller
{
public function index(ArticleService $service)
{
$articles = $service->fetchLatestArticlesForHomepage();
// عملية التحويل (Transformation) تتم بعد الكاش
return ArticleResource::collection($articles);
}
}
✅ شو اللي كسبناه بهاي الطريقة؟
- فصل الاهتمامات (Separation of Concerns): الـ Repository مسؤول عن جلب البيانات (وكيفية تخزينها مؤقتًا). الـ Service مسؤول عن منطق العمل. الـ Controller مسؤول عن استقبال الطلب وإرسال الاستجابة. كل واحد بيعمل شغلته.
- قابلية إعادة الاستخدام (Reusability): الآن أي جزء في النظام (API, Job, Command) بيقدر يطلب `ArticleRepository` ويحصل على نفس البيانات المكَيَّشة (Cached).
- سهولة الإبطال (Invalidation): لما يتم إضافة مقال جديد، كل اللي عليك تعمله هو حذف مفتاح كاش واحد: `Cache::forget(‘articles.latest.raw’)`. العملية واضحة ومركزية.
- سهولة الاختبار: يمكنك الآن اختبار كل طبقة بمعزل عن الأخرى.
المستوى الأعلى: عندما لا يكون Laravel هو المكان الصحيح
في مشروع ثاني اشتغلنا عليه، كان عبارة عن منصة محتوى عليها قراءة كثيفة جدًا (Read-heavy). ملايين الزوار بيقرأوا نفس المقالات كل يوم.
هنا، حتى وضع الكاش في الـ Repository ما كان الحل الأمثل. ليش؟ لأنه كل طلب لسه بوصل لـ PHP، وبشغّل Laravel، وبنفذ كود، وبحكي مع Redis أو Memcached… كل هاد استهلاك للموارد.
الحل كان عند المهندس اللي بيفكر كمهندس أنظمة. سألته: “وين حطيت الكاش؟”
جاوبني بهدوء: “مش في Laravel.”
الحل: الكاش على الحافة (Edge Caching)
الحل كان بسيط وعبقري: نقلنا مسؤولية الكاش من التطبيق إلى البنية التحتية اللي قبله.
- استخدام خدمة CDN مثل Cloudflare أو Fastly.
- جعل Laravel يخبر الـ CDN بكيفية التعامل مع الكاش.
الكود في Laravel صار أبسط ما يكون:
// ArticleController.php
public function show(Article $article)
{
// التطبيق فقط يجهز البيانات ويرسلها
$data = new ArticleResource($article);
// ... ويعطي تعليمات للبنية التحتية
return response($data)
->header('Cache-Control', 'public, max-age=300, s-maxage=900');
// max-age=300: للمتصفح (5 دقائق)
// s-maxage=900: للـ CDN والسيرفرات الوسيطة (15 دقيقة)
}
🎯 هنا العقلية تغيرت بالكامل:
- Laravel = مصدر الحقيقة ومنطق العمل (Source of Truth & Business Logic).
- الكاش = مسؤولية البنية التحتية (Infrastructure Concern).
النتيجة؟ 95% من الطلبات للمقالات ما كانت توصل لسيرفراتنا أصلاً! كانت Cloudflare بترد عليها مباشرة من أقرب مركز بيانات للمستخدم. أداء صاروخي وتوفير هائل في الموارد.
لماذا هذه العقلية فذّة فعلًا؟
المهندس الذي يفكر بهذه الطريقة يمتلك صفات أعمق:
- يفكر بنطاق التأثير (Blast Radius): قبل ما يكتب `Cache::remember`، بسأل حاله: لو صار خطأ في هذا الكاش، مين راح يتأثر؟ كيف ممكن أبطله بسرعة؟ ما هو أسوأ سيناريو؟
- يفهم تقلب البيانات (Data Volatility): مش كل البيانات زي بعض. بيانات إعدادات الموقع (config) شبه ثابتة، بينما بيانات المستخدم بتتغير. إحصائيات المقالات ممكن تتحمل تأخير، لكن عمليات الدفع لازم تكون لحظية. هو يختار استراتيجية الكاش بناءً على طبيعة البيانات، مش على مكان الكود.
- يرى الأداء كنظام متكامل: الأداء مش مجرد سطر كود. هو سلسلة بتبدأ من شبكة المستخدم، مرورًا بالـ CDN، ثم الـ Load Balancer، ثم سيرفر الويب، ثم PHP، ثم Laravel، ثم قاعدة البيانات، والعودة. هو يبحث عن نقطة الضعف في السلسلة كلها ويحسنها.
الخلاصة: الدرس الذي بدأ في غزة 🇵🇸
في غزة، تعلمنا أن الموارد شحيحة، والظروف قاسية، وأي خطأ صغير ممكن يكون مكلف جدًا. تعلمنا نبني أنظمة صامدة، مش أنظمة “فخفخة”.
أفضل مهندس قابلته هناك، واللي هو نفسه الشب اللي بدأ القصة، ما كان أسرع واحد بضيف كاش. كان أذكى واحد بيعرف متى وأين لا يضع الكاش.
واليوم، وهو يقود فرق هندسية عالمية، ما زال يردد نفس الجملة اللي بتلخص كل هالمقالة:
“الأداء ليس سطر كود… الأداء قرار معماري.” 🚀
لهيك المرة الجاي لما تفكر تضيف كاش، لا تسأل حالك “وين أحطه؟”. اسأل حالك: “ما هي طبيعة هذه البيانات؟ ومن هو الأجدر بالتعامل معها؟ التطبيق أم البنية التحتية؟”. الإجابة على هذا السؤال هي اللي بتصنع الفرق.
ودمتم سالمين يا حبايب.