بُني على أيدي مهندسين، للمهندسين.
البنية والأمان والتشغيل، مُفصَّلة لمن سيتكاملون مع هذه المنصّة، ويبنون وحدات فوقها، ويصادقون على مراجعة البنية. لا شيء هنا مُخفَّف لأغراض التسويق.
dotnet build CleverInit.slnx
0 Warning(s) · 0 Error(s)
dotnet test CleverInit.slnx
4,028 passed · 0 failed
- 4,028
- اختبارات آلية، host-solution
- 0
- تحذيرات البناء المسموح بها
- 5
- Behaviors لكل command
- 5
- خطوات تحديد المستأجر
كل قرار معماري متعمَّد. لا شيء أُدرج مصادفةً.
عشرة قرارات صاغت كل ما تبقّى.
كلٌّ منها مفروض في الشيفرة أو مُتحقَّق منه في CI — لا مُدوَّن في wiki على أمل الالتزام به.
- 01
Clean Architecture يفرضها المُصرِّف
Domain ← Application ← Infrastructure ← API هي مراجع مشاريع، لا اصطلاحات. أيّ handler يمدّ يده إلى البنية التحتية يُفشِل البناء.
- 02
pipeline واحد لكل طلب
التسجيل، والتتبّع، والتحقّق، وإبطال التخزين المؤقّت، والمعاملة — بالترتيب نفسه في كل مرة، بحيث يبقى التوقيت والتحقّق ودلالات الأخطاء موحّدة عبر كل endpoint.
- 03
قاعدة بيانات لكل عميل مُسجَّل
الحساب الذي يُنشأ عبر التسجيل يحصل على قاعدة بيانات SQL خاصة به، وتُشفَّر connection string الخاصة به عبر ASP.NET Core Data Protection قبل تخزينها.
- 04
عمليات القراءة إسقاطات مع عقد ترقيم صفحي
كل قراءة إسقاط no-tracking إلى DTO، وكل endpoint قوائم يُعيد نتيجة مُقسَّمة صفحيًا بسقفٍ صارم. مجموعة نتائج غير مقيّدة ليست خيارًا تتيحه الشيفرة.
- 05
لا SQL خام، في أي مكان
استعلامات EF Core بمعاملات فقط. لا تُقحَم مدخلات المستخدم في SQL أبدًا، لأن مسار بناء السلسلة النصية ببساطة غير موجود.
- 06
أحداث تصمد أمام العطل — بدلالات صادقة
ما يجب الإعلان عنه يُخزَّن في قاعدة البيانات إلى جانب العمل نفسه، ثم يَنشره sweeper واحد. التسليم مرة واحدة على الأقل، وعدد المحاولات محدود، وحالات dead-letter مُعلَنة لا مخفيّة.
- 07
الترحيلات append-only
الترحيل الذي شُحِن لا يُعدَّل أبدًا. كل تغيير في المخطّط هو ترحيل جديد إلى الأمام، في نفس commit تغيير الكيان الذي يستلزمه.
- 08
واجهات API خاضعة للإصدارات
تعمل واجهة HTTP على Asp.Versioning، فيكون التغيير الكاسر إصدارًا جديدًا صريحًا لا تعديلًا صامتًا يطرأ أسفل تكاملك.
- 09
الأخطاء وفق RFC 7807، بلغة واضحة
يتحوّل التحقّق إلى 400 مع خريطة أخطاء على مستوى الحقل، وعدم العثور إلى 404، والتكرارات إلى 409، وقواعد العمل إلى 422 — ويكون detail دائمًا جملة كاملة، لا اسم صنف ولا شذرة SQL.
- 10
على الوحدات أن تُثبت أنها تُلغي تحميلها
حمِّل وحدة، ثم أسقِط كل مرجع، وأجبِر عملية garbage collection، وتحقّق من أن المرجع الضعيف إلى load context الخاص بها قد مات. إن لم تستطع وحدةٌ إلغاء تحميلها فعليًا، فشل ذلك الفحص.
أربع طبقات، اتجاه واحد.
أسهم التبعية أدناه هي مراجع مشاريع. تجاوزها لا يُنتج تعليقًا في مراجعة الشيفرة — بل يُفشِل البناء.
- Domain الطبقة الأعمق · صفر تبعيات
- كيانات، value objects، domain events، domain exceptions، ثوابت.
- Application حالات الاستخدام
- يعتمد على Domain وحده. Commands، queries، handlers، validators، mappers، pipeline behaviors.
- Infrastructure مُحوِّلات
- يعتمد على Application وDomain. EF Core، Redis، MassTransit، JWT، BCrypt، repositories.
- API Composition root
- يعتمد على الطبقات الثلاث جميعًا. Controllers، middleware، dependency injection، OpenAPI، وتعيين problem-details.
الطبقات الداخلية لا تشير إلى الخارج أبدًا. يُفرَض الاتجاه حيث لا مجال للجدال فيه: في ملفات المشروع.
رسم بياني: أربع حلقات متّحدة المركز — API في الخارج، ثم Infrastructure، ثم Application، مع Domain في القلب. مراجع التبعية تشير إلى الداخل فقط؛ وأيّ مرجع يشير إلى الخارج يُفشِل البناء.
pipeline الطلبات.
يمرّ كل command عبر الـ behaviors الخمسة نفسها، من الأبعد إلى الأقرب. أما queries فتمرّ عبر خطوة المعاملة دون أن تمسّها.
01
التسجيل
اسم الـ command والمللي ثانية المنقضية. لا تُسجَّل الـ payloads أبدًا.
02
التتبّع
دورة حياة الـ handler كاملةً مُغلَّفة داخل span من OpenTelemetry.
03
التحقّق
يعمل كل validator مُسجَّل؛ ويصبح الفشل 400 مع خريطة أخطاء على مستوى الحقل.
04
إبطال التخزين المؤقّت
تُعلَّم مفاتيح التخزين المؤقّت على أنها غير صالحة بعد نجاح الـ command.
05
المعاملة
تُغلَّف الـ commands داخل معاملة EF Core. أما الـ queries فتتخطّى هذه الخطوة.
عميل واحد، قاعدة بيانات واحدة.
الحساب الذي يُنشأ عبر التسجيل يحصل على قاعدة بيانات SQL خاصّة به، تُجهَّز وتُطبَّق ترحيلاتها لحظة إنشاء الحساب.
تُشفَّر سلسلة الاتصال بتلك القاعدة عبر ASP.NET Core Data Protection قبل تخزينها.
مساحة الصورة — الأصل لاحقًا
رسم بياني: كيف تبقى بيانات حساب واحد مُنشأ عبر التسجيل منفصلة — قاعدة بياناته الخاصة، ومخططات الوحدات داخلها.
تحديد العميل الذي ينتمي إليه الطلب سلسلة من خمس خطوات، تتوقّف عند أول تطابق.
- 1مطالبة الرمز المُوقّعيحمل الرمز توقيعنا، ويُتحقَّق منه قبل الوثوق بالمطالبة التي بداخله.
- 2ترويسة X-Tenant-Idللاستدعاءات بين الخوادم التي لا تحمل رمز مستخدم.
- 3تطابق نطاق مخصّصنطاق العميل الخاص، مربوط بحسابه.
- 4النطاق الفرعي للمنصّةاسمه على نطاقنا، حين لا يأتي بنطاق خاص به.
- 5مُعامل في سلسلة الاستعلامبالاسم: في بيئة التطوير فقطأسهل قيمة في القائمة لكتابتها يدويًا، ولهذا تأتي في المرتبة الأخيرة.
عملٌ يصمد أمام العطل.
01
العمل يجري
تتغيّر الحالة وتُحفَظ. لم يلمس شيءٌ الشبكة بعد.
02
الإعلان يُخزَّن، لا يُرسَل
الرسالة التي تحتاجها بقية أجزاء النظام تُكتب في جدول قاعدة بيانات، إلى جانب العمل الذي أنتجها.
03
sweeper واحد ينشر
معالِج في الخلفية يجتاح الجدول وينشر ما يجده. لا شيء آخر في الـ codebase يتحدّث إلى الـ broker.
04
المستهلكون يتوقّعون التكرار
التسليم مرة واحدة على الأقل. يفحص المستهلكون مفتاح حدث مُعالَج أو يعتمدون على فهرس فريد، فتفشل المحاولة الثانية دون ضرر.
عدد المحاولات محدود. بعد عددٍ ثابت من الإخفاقات تُعلَّم الرسالة على أنها dead-letter ولا يُعاد محاولتها — نُفضّل قول ذلك على أن نوحي بعكسه.
الوحدات ضيوف داخل عملية المضيف.
تثبيت وحدة أو إزالتها لا يتطلّب إعادة تشغيل ولا إعادة نشر.
تُحمَّل كل وحدة داخل collectible assembly load context خاص بها. تثبيت وحدة لا يعيد تشغيل المضيف، وكذلك إزالتها.
العزل حقيقي، لا اسمي فقط. تُحمَّل تبعيات كل وحدة على نحو خاص بها. أما ما يأتي من المضيف فهو قائمة قصيرة وصريحة من العقود المشتركة — SDK الوحدات، وEF Core، وMediatR، وFluentValidation، ومكتبات ASP.NET و.NET الأساسية — إضافة إلى تجميعات العقود التي تنشرها الوحدات بعضها لبعض.
الفحص الذي يهمّنا هو ما يُثبت ذلك لا ما يدّعيه: حمّل وحدة، وأسقِط كل مرجع إليها، وأجبِر عملية جمع للنفايات، ثم تأكّد أن المرجع الضعيف إلى load context الخاص بها قد مات. فإن تعذّر تفريغ الوحدة تفريغًا حقيقيًا، فشل ذلك الفحص.
التوقيع توقيع RSA على تجزئة SHA-256، يُتحقَّق منه مقابل مفتاح عام يثبّته المشغّل. التثبيت من رابط URL يتطلّب شيئًا إضافيًا فوق التوقيع الصحيح: يجب أن يكون المضيف المصدر ضمن قائمة سماح، وتلك القائمة تُسلَّم فارغة، فالتثبيت من رابط لا يفعل شيئًا حتى يضيف المشغّل مصدرًا إليها.
النشر — حدث على مستوى المضيف
- توقيع الـ artifact — توقيع RSA على تجزئة SHA-256 — يُتحقَّق منه مقابل مفتاح عام يُثبّته المُشغِّل.
- تُحمَّل الـ assemblies داخل assembly load context خاص وقابل للتجميع.
- يُسجّل الـ manifest الوحدة في الـ marketplace. دون إعادة نشر.
التثبيت — حدث على مستوى المستأجر
- تعمل الترحيلات في المخطّط الخاص بالوحدة، داخل قاعدة بيانات ذلك العميل.
- تُحدَّث القائمة وقائمة الصلاحيات فورًا.
- إلغاء التثبيت يُطفئ الوحدة ويحتفظ ببياناتها — أما إسقاط الجداول فخيار منفصل ومقصود.
تُعلن الوحدة عن ماهيّتها في ملف واحد:
{
"slug": "chat",
"name": "Chat",
"version": "1.0.0",
"sdkVersion": "1.1.0",
"entryAssembly": "CleverInit.Module.Chat.dll",
"schema": "chat",
"dependencies": [],
"frontend": {
"remoteEntry": "panel/remoteEntry.json",
"exposedModule": "./Routes"
},
"navSections": [
{ "items": [
{ "label": "Chat", "routerLink": null, "children": [
{ "label": "chat.menu.conversations",
"routerLink": "/m/chat/conversations" }
] }
] }
],
"permissions": [
{ "name": "Chat.View", "displayName": "View chat" },
{ "name": "Chat.ManageBots", "displayName": "Manage bots" }
],
"dashboardWidgets": [
{ "key": "chat.unread", "requiredPermission": "Chat.View" }
]
}مجموعة الاختبارات هي العقد.
تعمل 4,028 اختبارًا آليًا على الـ host solution — وتأتي مجموعات اختبارات الوحدات فوق ذلك، وتُتابَع لكل وحدة على حدة. القاعدة قاطعة: «تمّ» يعني أن الـ solution كاملةً تُبلّغ عن صفر إخفاقات.
اختبارات الـ host-solution حسب الطبقة، كما تُتابَع في README المستودع. تُحسَب مجموعات اختبارات الوحدات على حدة.
أهداف التغطية لكل طبقة من الميثاق، بالنسبة المئوية.
- أيّ بناءٍ فيه تحذيرٌ واحد هو بناء ناقص. الحدّ هو 0 تحذيرات و0 أخطاء — عند كل commit.
- تشحن كل وحدة اختبار تكافؤ: أيّ controller يطلب صلاحية لا يعلنها الـ manifest الخاص به يُفشِل البناء.
- كل تغيير ذي معنى يستقر في سجل تدقيق append-only — append-only عبر التطبيق، لا مختومًا تشفيريًا في وجه من يملك وصولًا مباشرًا إلى قاعدة البيانات.
مُثبَّتة، ومتعمَّدة، ومملّة.
الإصدارات مُثبَّتة، وإضافة أيّ مكتبة تتطلّب موافقة صريحة. هذا ما يوجد في الـ solution اليوم — لا قائمة أمنيات.
Backend
.NET 10 · Clean Architecture · CQRS
- .NET 10 · C# 13 · ASP.NET Core
- EF Core 10 · SQL Server
- MediatR 14 · FluentValidation 12
- Mapperly 4.3 · Ardalis.Specification 9.3
- Redis · MassTransit + RabbitMQ 8.5
- BCrypt · Otp.NET · Asp.Versioning
- Scalar OpenAPI
- xUnit · Shouldly · NSubstitute
Panel
Angular 21 · zoneless · signals
- Angular 21, zoneless change detection
- الحالة محفوظة في signals
- واجهات الوحدات الأمامية بوصفها remotes تُحمَّل وقت التشغيل
- Vitest
- الإنجليزية · الهولندية · الألمانية
التشغيل
صغير عن قصد
- Docker · docker-compose للإقلاع المحلي
- spans من OpenTelemetry في pipeline الطلبات
- تسجيل مُهيكَل — الأسرار والـ tokens والـ PII لا تصل إلى أيّ sink أبدًا
- RFC 7807 عبر كامل سطح الأخطاء
الأرقام وراء تسجيل الدخول.
موجزٌ لأجل الاستبيان — الصورة الكاملة على صفحة الأمان.
- Access tokens
- تنتهي صلاحيتها خلال 15 دقيقة. تنتقل الصلاحيات على هيئة claims، فتكون فحوص التفويض عمليات بحث في الذاكرة.
- Refresh tokens
- قيم عشوائية بطول 256 بت، تُخزَّن كتجزئة فقط، أحادية الاستخدام، وتُدوَّر مع كل عملية refresh.
- كلمات المرور
- BCrypt بعامل عمل 12، مثبَّت في الشيفرة. قفلٌ بعد 5 محاولات فاشلة افتراضيًا — قابل للضبط لكل مستأجر.
- رموز لمرة واحدة
- رموز من 6 أرقام عبر البريد الإلكتروني أو SMS، مُجزَّأة عند التخزين، تنتهي خلال 5 دقائق بحدٍّ أقصى 3 محاولات.
ثلاث إجابات صريحة.
- توجد بوابة API عامة. تغطّي واجهات API العامة للمنصّة — تلك بالضبط، ولا شيء أكثر. وما هو داخلي يبقى داخليًا.
- Kubernetes هو الوجهة التي تتّجه إليها المنصّة. أما أيّ مزوّد سيُشغّله فأمرٌ لا ننشره عن قصد — ذلك الصمت قرار أمني، لا سهوٌ.
- لا فوترة قائمة على الاستخدام ولا احتساب تناسبي — لا اليوم، ولا في الخطط. الفاتورة التي يمكنك التنبّؤ بها هي الميزة نفسها.