عندما يتحول المشروع إلى فوضى
\nهل سبق لك أن فتحت مشروع C# كتبته قبل عام، وأدركت برعب أن لا شيء يمكن تغييره دون خطر كسر كل شيء؟ منطق الأعمال متشابك بإحكام مع Entity Framework، وحدات تحكم ASP.NET تخزن أسرار قاعدة البيانات، واختبارات الوحدة تتحول إلى تكامل جهنمي مع قاعدة بيانات حقيقية. هل هذا مألوف؟
\nهذا عرض كلاسيكي لغياب العمارة. الخبر السار: هناك وصفة مجربة - العمارة السداسية (المعروفة أيضًا باسم Ports & Adapters). هذا النمط، الذي ابتكره أليستر كوكبيرن، ليس مجرد مصطلح عصري. إنه خلاص للمشاريع المتوسطة والكبيرة على C#. دعنا نفهم كيف يعمل، ونكتب الكود فورًا.
\n\nجوهر العمارة السداسية: النواة والمحيط
\nتخيل شكلًا سداسيًا (hexagon). في المركز - تطبيقك مع منطق أعمال نقي، مكتوب بلغة C# خالصة بدون تبعيات خارجية. على الحواف - المنافذ (واجهات)، التي تتواصل من خلالها النواة مع العالم الخارجي. المحولات (Adapters) هي التطبيقات الملموسة: وحدات تحكم ASP.NET، مستودعات EF Core، عملاء RabbitMQ.
\nالقاعدة الرئيسية: النواة لا تعرف شيئًا عن العالم الخارجي. إنها تعرف فقط منافذها. هذا يسمح بتغيير قاعدة البيانات، واجهة المستخدم، أو قوائم الرسائل دون تغيير منطق الأعمال.
\n\nلماذا هذا مهم لمطور C#؟
\n- \n
- قابلية الاختبار: يمكن اختبار منطق الأعمال بدون قاعدة بيانات، HTTP، أو نظام ملفات. \n
- المرونة: هل تريد استبدال PostgreSQL بـ MongoDB؟ فقط غير المحول. \n
- قابلية القراءة: يصبح الكود أشبه بمواصفات المتطلبات، وليس كتلة من التبعيات. \n
- الالتزام بـ SOLID: خاصة مبدأ عكس التبعيات (Dependency Inversion). \n
هيكل المشروع في C#
\nفي العمارة السداسية، عادةً ما يتم تمييز ثلاث طبقات رئيسية. بالنسبة لـ C#، من المناسب تنظيمها كمشاريع منفصلة في حل واحد:
\n\nMyApp.sln\n├── MyApp.Domain // النواة: الكيانات، value objects، المنافذ (واجهات)\n├── MyApp.Application // حالات الاستخدام (خدمات التطبيق)\n├── MyApp.Infrastructure // المحولات: EF Core، واجهات برمجية خارجية، تسجيل\n└── MyApp.Api // محول الدخول: ASP.NET Controllers / Minimal API\n\n\nلاحظ: Domain و Application هما مكتبات فصول نقية بدون مراجع لـ Microsoft.AspNetCore أو EntityFramework. فقط System و System.Threading.Tasks.
نكتب الكود: مثال واقعي
\nلنفترض أننا نبني نظام طلبات. نحتاج إلى إنشاء طلب وحفظه.
\n\nالخطوة 1. النواة (Domain)
\nهنا تعيش الكيانات والمنافذ - الواجهات التي تحدد كيف تتواصل النواة مع العالم.
\n\n// MyApp.Domain/Entities/Order.cs\npublic class Order\n{\n public Guid Id { get; private set; }\n public string CustomerName { get; private set; }\n public decimal TotalAmount { get; private set; }\n public DateTime CreatedAt { get; private set; }\n\n public Order(string customerName, decimal totalAmount)\n {\n Id = Guid.NewGuid();\n CustomerName = customerName;\n TotalAmount = totalAmount;\n CreatedAt = DateTime.UtcNow;\n }\n}\n\n// MyApp.Domain/Ports/IOrderRepository.cs (منفذ خروج)\npublic interface IOrderRepository\n{\n Task<Order> GetByIdAsync(Guid id);\n Task AddAsync(Order order);\n}\n\n// MyApp.Domain/Ports/IOrderNotifier.cs (منفذ خروج - إشعارات)\npublic interface IOrderNotifier\n{\n Task NotifyOrderCreatedAsync(Order order);\n}\n\n\nلاحظ: IOrderRepository و IOrderNotifier هما منافذ. النواة تحدد ماذا تريد، ولكن ليس كيف يتم تنفيذه.