الانتقال إلى المحتوى الرئيسي

لوحة الأوامر

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

إيه اللي بيحصل ورا الكواليس: رحلة abdullahhakim.me (الجزء التاني) — جوه .NET 10

كتب بواسطة
Abdullah Hakim
نُشر في
--
المشاهدات
212
إيه اللي بيحصل ورا الكواليس: رحلة abdullahhakim.me (الجزء التاني) — جوه .NET 10

جوه .NET 10: Clean Architecture & CQRS

في الجزء الأول، تتبعنا رحلة الـ Request من الـ Browser لحد ما وصل للـ Server. شوفنا إزاي الـ DNS، TLS، Nginx، وDocker بيشتغلوا مع بعض عشان يوصلوا الـ Traffic بأمان وكفاءة.

بس الـ Routing ده نص القصة بس.

لما الـ Request يدخل جوه الـ ASP.NET Core Web API، بيلاقي نفسه في ecosystem متصممة للـ clarity، testability، والـ scale. البوست ده عن مخ Hakim: الـ .NET Backend. بالتحديد، إزاي بنايته باستخدام Clean Architecture و CQRS من خلال MediatR — وإزاي ده مش مجرد enterprise over-engineering، ده اختيار مقصود يخلي الـ personal platform تتعامل زي الـ production software.


ليه Clean Architecture؟

لو شغالت في monolith قبل كده والـ business logic طلع من الـ controllers لكل حتة، أو تغيير بسيط في schema كسر نص الـ application، فأكيد عارف الألم.

الـ N-layer architecture التقليدية (Presentation → Business → Data) سهلة في البداية، بس الـ dependencies بتلخبط بسرعة.

Clean Architecture (اللي Robert C. Martin شهّرها) بتعكس الكلام ده. الفكرة بسيطة:

الـ Domain Layer معرفش حاجة عن الـ database، الـ UI، أو الـ framework.

الـ Dependencies بتدخل لجوه. النص هو pure business logic، والـ outer layers هم الـ infrastructure concerns.

ده إزاي بنايت الـ Hakim Backend:

plaintext
  • Hakim.Domain: صفر NuGet dependencies. مجرد C# classes، enums، وlogic. معرفش إن Entity Framework موجود أصلاً.

  • Hakim.Application: بتدير الـ use cases من خلال MediatR. بتعرف interfaces (زي IAppDbContext، ICacheService) والـ Infrastructure لازم تimplementهم.

  • Hakim.Infrastructure: الـ "dirty" layer. بتكلم PostgreSQL من خلال EF Core، وبتعمل cache على Redis.

  • Hakim.Api: الـ shell الرفيعة. الـ Controllers بس بيdeserialize الـ HTTP requests ويبعتوها للـ MediatR. مفيش business logic هنا خالص.

الـ separation ده معناه إني أقدر أغيّر database provider أو cache strategy من غير ما لمّس سطر واحد في الـ domain code.


CQRS: فصل الـ Reads عن الـ Writes

في الـ CRUD API العادي، نفس الـ model بيستخدم لـ create وupdate وread. ده بيمشي لحد ما يقف. الـ Reads والـ Writes عندهم احتياجات مختلفة جوهرياً:

  • Queries لازم تكون سريعة، غالباً denormalized، وcache-friendly.

  • Commands محتاجة validation، business rules، وtransactional integrity.

CQRS بيفصلهم. في Hakim، كل operation إما Command (بتغيّر state) أو Query (بترجّع data). MediatR بتلعب دور الـ dispatcher.

خلينا نشوف الـ اتنين في الواقع.


The Query Side: قراءة Blog Post

لما بتفتح بوست، الـ Frontend بيعمل query للـ API. لاحظ إزاي الـ controller رفيع، بيعتمد على [ETag] للـ browser-level caching وMediatR للـ شغل التقيل:

csharp

الـ Controller معرفش إزاي يجيب الـ post. بس بيبني GetPostBySlugQuery ويبعته.

الـ Handler هو اللي فيه الـ performance magic:

csharp

ليه الكلام ده مهم؟

  1. Compiled EF Core Queries: EF Core بيcompile الـ expression مرة واحدة ويعيد استخدام الـ execution plan، بيوفر milliseconds في كل database call.

  2. Redis as a Read Replica: لو بوست جاله traffic spike (مثلاً بعد LinkedIn post)، الـ database بيتضرب مرة كل 10 دقايق. كل request تاني بيخدم من الـ memory في microseconds.

  3. Value Objects: لاحظ new Slug(request.Slug). ده بيضمن إن الـ slug validation والـ formatting rules يكونوا محصورين في الـ domain، مش متنثريين في كل query.


The Command Side: إنشاء Post

الـ Queries سهلة. الـ Commands هي اللي فيها الـ business rules. عشان الـ blog بتاعي بيدعم English وArabic، الـ CreatePostCommand بتقبل localized translations:

csharp

والـ Handler بيضمن الـ data integrity وcache invalidation:

csharp

لاحظ إزاي الـ write operation بيتعامل显式 مع الـ side effects زي إزالة الـ stale list data من Redis (RemoveAsync)، عشان الـ read side يفضل eventually consistent.


Pipeline Behaviors: الـ Secret Sauce

من أقوى حاجات MediatR هي الـ Pipeline Behaviors. دول زي middleware بيلفوا حوالين كل request handler، وبيخلوا الـ core logic نضيف.

في DependencyInjection.cs، بسجل global ValidationBehavior:

csharp

قبل ما أي command handler يشتغل، FluentValidation بتعمل check على الـ request. لو الـ validation فشلت، بيترمي ValidationException والـ Handler ما بيشتغلش أصلاً.

csharp

دي قوة الـ pipeline: الـ handlers بيفضلوا مركزين على الـ domain operations بس، والـ infrastructure والـ validation concerns بيتلّفوا حواليهم.


Performance في الواقع

ممكن تسأل: كل الـ abstraction ده فعلاً بيفرق في blog شخصي؟

في الـ cold start، query للـ blog post من PostgreSQL بياخد ~45ms. مع الـ compiled query وRedis cache، الـ requests اللي وراه بتوصل لـ ~2ms. الـ command pipeline بيضمن إني مش هقدر أدخل invalid slug أو أنشر بوست من غير valid culture code.

وأهم من كده، الـ architecture بتscale بشكل horizontal. لو Hakim محتاج يشيل 10,000 concurrent reader، أقدر أشغل API containers أكتر ورا Nginx من غير ما أكتب سطر handler جديد. الـ state عايش في PostgreSQL وRedis؛ الـ application layer stateless.


إيه اللي جاي؟

في الجزء الأول، تتبعنا الـ highway. في الجزء التاني، فتحنا المحرك.

بس Hakim مش blog بس. تحت الهود، فيه microservice منفصل بيراقب job boards، بيعمل scrape للـ listings، وبيستخدم LLM عشان يفصّل الـ resume في real-time — كل ده بيتtrigger من background worker شغال في نفس الـ Docker host.

في الجزء التالت، هنشيل الـ curtain عن الـ AI-driven resume tailoring pipeline: إزاي الـ FastAPI microservice بيعمل scrape، إزاي الـ .NET background service بيorchestrate الـ workflow، وإزاي Groq + Reactive Resume API بيولّدوا tailored PDF ويبعتوه عبر SMTP — fully automated، fully decoupled.

شكراً للقراءة.

آخر تحديث: --
إيه اللي بيحصل ورا الكواليس: رحلة abdullahhakim.me (الجزء التاني) — جوه .NET 10 | Abdullah Hakim