ابحث عن أمر لتشغيله...

في وقت من الأوقات، CQRS مع MediatR بطّلت تبقى قرار تصميم وبقت هي الخيار الجاهز لأي باك إند .NET. مشروع جديد، قالب Clean Architecture، وMediatR يتركّب قبل ما أول entity تتكتب. أنا جرّبت التركيبة دي في نظامين حقيقيين — منصة البلوج بتاعتي اللي شغالة في الإنتاج، ومشروع التخرج بتاعي — وسيبتها برّه نظام تالت عن قصد. والفرق بين الاختيارات دي هو الجزء اللي الشروحات بتعدّيه.
شيل رسومات الباترن على جنب، وMediatR في الآخر وسيط شغال جوّه نفس العملية: بتبعت أوبجكت طلب، وhandler واحد بعينه هو اللي بيرد عليه، ومفيش طرف فيهم بيكلّم التاني مباشرة. فكّ الارتباط ده كويس، بس مش هو المكسب الحقيقي. المكسب الحقيقي هو الـ pipeline: الحاجات اللي بتتكرر على كل طلب — الـ validation والتسجيل وحدود الـ transactions — بتتلفّ حوالين كل request في behaviors، تتكتب مرة واحدة وتشتغل في كل حتة.
في باك إند البلوج بتاعي، كل command وquery بيعدّي على validation behavior قبل ما يوصل لأي handler:
قواعد FluentValidation بتتسجّل لكل نوع طلب، والـ behavior بيشغّلها كلها، والطلب الغلط عمره ما بيوصل لمنطق البيزنس. الـ handlers بتوعي بيشتغلوا على أساس إن الإدخال سليم — مش عشان أنا واثق في اللي بيبعت، لكن عشان الـ pipeline بيضمن ده. دي أحلى صفقة بيقدمها MediatR: شوية لفّ ودوران في المقابل إن كل حاجة مشتركة تعيش في مكان واحد بس.
المكسب التاني هو الشكل. handler واحد لكل use case بيخلي طبقة الـ Application تتقرى زي فهرس:
مفيش service عملاقة بأربعين method. كل use case وحدة صغيرة لوحدها وتتاختبر بسهولة، وإضافة feature معناها تضيف ملفات جديدة، مش تعدّل في service مشتركة لحد ما تقاوم التغيير.
الـ validation هو الـ behavior اللي كل الشروحات بتتكلم عنه — وهو النهارده الـ behavior الوحيد المسجّل فعلًا في كود El-Hakim. الـ transaction behavior هو النص التاني من التصميم، واللي بلجأ له لما command لازم ينجح أو يفشل كوحدة واحدة: أي command يدخل الـ pipeline بيتنفّذ جوّه database transaction — commit لو الـ handler خلّص على خير، وrollback لكل اللي اتعمل لو وقع في النص.
ليه ده يعيش في الـ pipeline مش جوّه كل handler؟ عشان حدود الـ transaction قاعدة على مستوى الـ use case كله، مش قرار يخص كل handler لوحده. لو كل handler فتح transaction بنفسه، هتلاقي أوامر بتتنفّذ نصّها جوّه transaction ونصّها برّاها، لمجرد إن حد نسي. الـ pipeline بيخلي القاعدة مستحيلة تتفوّت: كل command جوّه transaction، دايمًا، من غير ما أي حد يكتب handler يحتاج يفتكر.
El-Hakim هي المنصة الشخصية بتاعتي — بلوج وبورتفوليو وطلبات شغل وتعليقات وميديا ولايكات. شغالة Clean Architecture على .NET 10، وطبقة الـ Application منظمة commands وqueries عن طريق MediatR. عدد الـ features هو بالظبط الوضع اللي الباترن ده معمول عشانه: use cases كتير، وقواعد validation مشتركة، وكود بيز أنا بصينه لوحدي، فالاتساق عندي أهم من الشطارة. ولما كل feature بتدخل من نفس الـ pipeline، عمري ما بحتاج أفتكر أنهي controller عمل validation وأنهي نسي يعملها.
وأغلب الكلام عن CQRS بيركّز على أوامر الكتابة، بس نص الشغل عندي قراية. دي الـ query اللي ورا قائمة مقالات البلوج:
تلات حاجات بتفرق هنا: الـ AsNoTracking عشان دي قراية بس ومفيش تعديل هيتبعها؛ والـ projection بيحصل جوّه الاستعلام نفسه، فاللي بيرجع من الداتابيز هو الـ DTO اللي الصفحة محتاجاه بالظبط — لا entity كاملة تتمسك عشان تتحوّل وتترمي؛ والـ pagination بيحصل في الداتابيز نفسها قبل ما أي سطر يعدّي. الـ queries مش محتاجة behaviors متراكمة فوقها، ومش محتاجة تمشي في نفس الطريق اللي الأوامر بتمشي فيه. عدم التماثل ده هو مغزى فصل القراية عن الكتابة أصلًا.
AIO Academic Hub، مشروع تخرجي، هو المكان اللي اتعلمت فيه الحدود. تحت ضغط تسليمات امتحانات في نفس اللحظة، النظام خبط في lock contention في الداتابيز. طبّقنا CQRS مع MediatR — بس خليني أبقى دقيق في مين صلّح إيه. فصل مسار الكتابة خلّى فلو التسليم واضح، والوضوح ده هو اللي سمح لنا نركّب الحاجات اللي خفّفت الـ contention فعلًا: كاش Redis للجلسات وbackground queues للشغل التقيل. الـ CQRS ما شالش ولا lock واحد؛ هو بس عملّنا نقطة وصل نظيفة يتلزق عليها الحل. ده كلام أهدى من اللي أغلب كتابات CQRS بتقوله، وهو الكلام الصادق.
GeoGuard هو API بـ .NET بنيته كتقييم تقني: حظر IPs على حسب الدولة، وبشرط صارم — مفيش داتابيز خالص. كل الـ state عايشة في collections آمنة للتعامل مع أكتر من thread في نفس الوقت: ConcurrentDictionary للدول المحظورة وConcurrentQueue لسجل محاولات الدخول. والـ use cases اللي عندي قليلة وعلى قدّ الإيد: احظر دولة، فكّ حظرها، افحص IP، واقرا سجل المحاولات.
بالحجم ده، MediatR كان هيبقى تقديس للباترن مش أكتر. كتبت في README المشروع إن طبقة Application منفصلة اتشالت عن قصد — الـ use cases مباشرة بما يكفي إن فصلها كان هيخلق طبقات وتوصيلات زيادة مالها لازمة، فالدومين نفسه شايل التنسيق. وservice عادية هي اللي بتعمل الشغل:
والـ store اللي في الذاكرة بيورّيك حجم الموضوع كله على بعضه:
دي طبقة التخزين كلها. لا DbContext ولا migrations ولا احتفالات repository فوق EF — ديكشنري آمنة للـ threads ومحطوطة ورا interface، عشان شرط الـ no-database خلّى الذاكرة هي التصميم الصادق. ولو الشرط ده اتغيّر في يوم، الـ interface هي نقطة الوصل؛ والـ service اللي فوقها مش هتتحرك.
مفيش mediator، مفيش handlers، مفيش validators — وكمان لا AutoMapper ولا FluentValidation، لنفس السبب. الكود أقصر، والـ call stack واضح ومباشر، واللي بيراجع يقدر يقرا الفلو كله في قعدة واحدة. إنك تسيب باترن برّه كان قرار تصميم، ومتوثّق زي أي قرار.
تكاليف MediatR حقيقية، بس هادية. الـ debugging معناه إنك بتمشي جوّه الـ mediator بدل ما تنطّ على التنفيذ على طول، و«اذهب إلى التعريف» بيودّيك على interface مش على الـ handler نفسه. وكل use case بتكلّفك كذا ملف — request وhandler وvalidator — وده تمن عادل عند خمسين use case، وطقوس على الفاضي عند خمسة. واسم الباترن نفسه بيعمل لخبطة: CQRS فكرة معمارية عن فصل القراية عن الكتابة، وMediatR مكتبة. تقدر تفصل موديلات القراية والكتابة من غير mediator خالص، وتقدر تستخدم mediator بموديل واحد مشترك — وأغلب الكود بيزات اللي اسمها «CQRS»، بتاعتي من ضمنهم، بتعمل التانية ولازم تعترف بده.
ونمط الفشل اللي بشوفه أكتر حاجة هو تطبيق CRUD لابس لبس مؤسسات: أربع entities وعمليات إنشاء وقراية وتعديل وحذف أساسية، وpipeline CQRS كامل بيضيف بطء على التطوير بدل ما يشيل تعقيد من النظام. الباترن ده اتعمل أصلًا لأنظمة مسار الكتابة ومسار القراية فيها بيشدّوا في اتجاهات مختلفة فعلًا. ولو بتوعك مش كده، الـ pipeline بيبقى بوابة رسوم على طريق مفيهوش زحمة أصلًا.
امسك في MediatR لما الحاجات المشتركة — validation وتدقيق وtransactions — تحتاج بيت واحد على use cases كتير.
امسك فيه لما عدد الـ features بيكبر والـ service المشتركة بتتحوّل لمخزن كراكيب.
سيبه لما الـ use cases قليلة ومباشرة ومش متوقّع تتكاثر — service عادية أسهل في القراية والتصحيح والحذف.
سيبه لما القيد نفسه هو الصِّغَر: تقييمات ونماذج أولية وخدمات لغرض واحد.
ولو حطينا التلات أنظمة جنب بعض، الصورة بتبقى أوضح من أي شرح:
El-Hakim — pipeline MediatR: features كتير وقواعد validation مشتركة وكود بصينه لوحدي، فالاتساق بيكسب والـ pipeline دافع تمنه.
AIO Academic Hub — CQRS كنقطة وصل: ما صلّحتش الـ locks بنفسها، بس خلّت إضافة كاش Redis والـ background queues ممكنة من غير ما نفكّ النظام.
GeoGuard — services عادية من غير mediator: أربع use cases وشرط no-database صارم؛ الـ pipeline هناك كانت هتبقى وزن زيادة على الفاضي.
الباترنز أدوات بفواتير. CQRS مع MediatR دفع تمنه في نظامين من بتوعي، وكان هيفرض ضريبة على التالت على الفاضي. المهارة مش في حفظ الباترن — المهارة إنك تعرف كل نظام عندك محتاج أنهي قرار.
شغّلت CQRS مع MediatR في نظامين حقيقيين — وسيبته برّه التالت عن قصد. المهارة مش إنك تعرف الباترن، المهارة إنك تعرف حدوده فين.
في الجزء الأول، تتبعنا رحلة الـ Request من الـ Browser لحد ما وصل للـ Server. في الجزء التاني، بنفتح المحرك. بوست عميق عن إزاي الـ Hakim Backend متسطب باستخدام Clean Architecture و CQRS عبر MediatR — من compiled EF Core queries و Redis caching لحد validation pipelines و domain events. نظرة على ليه الـ enterprise-grade patterns بتفرق، حتى في blog شخصي.
غوص عميق تحت كابوت دوت نت: رحلة كود CSharp من Roslyn و CIL للـ Native CPU Assembly. شرح الـ Method Tables والـ JIT Trampolines، والـ Tier 0/1 JIT، والـ Dynamic PGO، والـ OSR، والـ ReadyToRun والـ Native AOT.