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

قرار معماري: Fastify للمحركات المشتركة (لا NestJS)

:::info سجلّ مُنعكَس — الإصدار 1.2 · المصدر الأصلي: قرار معماري داخلي، 10 أغسطس 2026 تُعيد هذه الصفحة إنتاج قرار معماري داخلي (إضافة لميثاق برنامج الوحدات المنصّية المشتركة، القسم 6 — حزمة التقنيات) يغطّي سبب تأليف المحركات الخمسة المشتركة كإضافات Fastify صِرفة بدلاً من تغليفها بـNestJS. أُعيد بناؤه من المحتوى الفعلي للوثيقة الأصلية، بنفس المعاملة المُطبَّقة على قرار معماري: كيف يصل المستودع إلى قاعدة البيانات. يدعم هذا القرار مباشرة القسم 6 من السياسة والقسم 6-1. :::

إضافة لـ: ميثاق برنامج الوحدات المنصّية المشتركة، القسم 6 (حزمة التقنيات) | الإصدار 1.2 | 10 أغسطس 2026

يُلغي: الإصدار 1.1 (9 أغسطس 2026) — نقاش فريق غير رسمي + نقدان معماريان مستقلان. يُضيف الإصدار 1.2 مصفوفة تقييم مرجّحة من 12 معياراً أنتجها الفريق، ويُزيل دافع "لا تكلفة ترحيل" — لم يبدأ التنفيذ بعد، فلم يكن أساساً صالحاً للقرار أصلاً.

1- القرار​

تُنفَّذ المحركات المشتركة الخمسة (Entitlements، Auth/RBAC، Payment، Notifications، Audit) كإضافات Fastify صِرفة، تُسجَّل داخل نظام monolith وحيد العملية خاص بكل قطاع. لا تُدخَل NestJS كإطار تطبيق يُغلِّف المحركات.

هذا قرار إطار عمل داخل القطاع فقط. لا يُغيّر الفصل القائم بين القطاعات في المنصّة: يعمل كل قطاع (عَمَار، Clinivo، EduSuite، وقطاعات مستقبلية) ببيئة تشغيل وقاعدة بيانات ونشر معزولة خاصة به بالفعل، مستهلِكاً المحركات المشتركة كحزم مُرقَّمة. شيفرة المحرك مشتركة؛ بيئة تشغيله ليست كذلك — هذا صحيح منذ حُدِّدت المحركات، ولا يُغيّر هذا القرار ذلك.

2- السياق​

  • أثار نقاش فريق سؤالاً حول ما إذا كان يجب تغليف المحركات بـNestJS لكسب حقن التبعيات (DI) والحرّاس (Guards) والمُعترِضات (Interceptors).
  • رُوجِع نقدان مكتوبان مستقلان لتبرير Fastify الأصلي لاحقاً. يُعامَل كلاهما كمُدخلات لهذا القرار المعماري، لا كالقرار نفسه — يُسجِّل القسم 5 ما أصاب فيه كل نقد وما لا يقبله هذا القرار.
  • تنصّ وثيقة متطلبات البرنامج (القسم 6، حزمة التقنيات) بالفعل على Fastify كالخيار المُنفَّذ؛ يستبدل هذا القرار المعماري التبرير غير الموثَّق سابقاً وراء ذلك السطر بتبرير صريح وقابل للدفاع عنه.

3- دوافع القرار (مُنقَّحة)​

أُعيدت صياغة كل دافع أدناه لإزالة الادّعاءات التي لم يكن التبرير الأصلي يدعمها فعلياً.

3-1 بيئة تشغيل واحدة، دورة حياة واحدة، لا متطلب غير محلول​

  • داخل عملية القطاع، تتركّب المحركات الخمسة بالفعل عبر شجرة إضافات Fastify — دورة حياة HTTP واحدة، ومجموعة خطّافات واحدة، ونموذج تسجيل واحد. سيجلس Nest كطبقة تجريد تطبيق/وحدة ثانية فوق حد تُحدِّده Fastify بالفعل، دون معالجة متطلب غير محقَّق حالياً.
  • الصياغة الصحيحة لرفض Nest ليست "لا تستطيع Nest فعل هذا" — تملك Nest وحدات وDI وحرّاساً ومُعترِضات ومُوائم Fastify، وتستطيع تقنياً. الصياغة الصحيحة هي أن Nest لا توفّر قيمة معمارية إضافية كافية هنا لتبرير إدخال تجريدها فوق معمارية إضافات Fastify التي تُحدِّد بالفعل حدود الوحدات المطلوبة.

3-2 التغليف حد تركيب، لا حد تفويض — مُصحَّح​

  • ادّعاء مُتراجَع عنه: نصّت نسخة سابقة من هذا التبرير على أن تغليف إضافات Fastify "هو تحديداً نموذج الحدود الذي يعتمد عليه نظام الوحدات المبني على الاستحقاقات." هذا يبالغ في ما يوفّره التغليف.
  • البيان المُصحَّح: يوفّر تغليف Fastify حد بيئة التشغيل/الوحدة الذي يُركَّب ضمنه تطبيق الاستحقاقات والخطّافات والمُزخرِفات والمخططات باتساق. ليس هو حد التفويض نفسه. يبقى التحقق من الاستحقاقات وRBAC منطق أعمال صريح من جانب الخادم (خطّاف requirePermission/requireEntitlement المُحدَّد بالفعل في وثائق متطلبات محركَي Entitlements وIAM)، يُطبَّق بنفس الشكل بغضّ النظر عن إطار التطبيق الجالس فوق Fastify.
  • النتيجة: ليس هذا القرار حجة بأن حرّاس Nest "لا يستطيعون" تطبيق الاستحقاقات — يستطيعون. إنه حجة بأنهم سيُعيدون تغليف منطق تتعامل معه وسيطة Fastify مباشرة بنفس الفعالية، بتكلفة طبقة إطار ثانية.

3-3 قابلية نقل المحركات عبر قطاعات مُشغَّلة باستقلالية​

  • بما أن شيفرة المحرك مشتركة لكن بيئة تشغيله معزولة لكل قطاع، يجب أن تبقى المحركات قابلة للاستهلاك من أي تطبيق قطاع قائم على Fastify، بغضّ النظر عمّا يُغلِّفه ذلك القطاع حول مساراته الخاصة.
  • يحافظ تأليف المحركات كإضافات Fastify صِرفة (دوال، بلا مُزخرِفات خاصة بإطار) على هذا: يستهلكها القطاع بتسجيل الإضافة، سواء كان تطبيق ذلك القطاع نفسه يُشغِّل Nest-on-Fastify لمنطق أعماله الخاص أم لا.
  • سيفرض تأليف المحركات كوحدات Nest بدلاً من ذلك قيداً أثقل بكثير على كل مستهلِك: سيُطالَب كل قطاع حالي ومستقبلي بأن يكون تطبيق Nest لمجرّد تسجيل محرك مشترك. هذه تكلفة قابلية نقل بلا فائدة مقابلة، إذ لا تحتاج أي قدرة محرك فعلياً حقن تبعيات Nest لتعمل.

:::note أُزيل في الإصدار 1.2 أدرجت نسخة سابقة من هذا القرار "لا تكلفة ترحيل" كدافع (§3-4)، على افتراض أن المحركات مبنية بالفعل. هذا الافتراض غير صحيح — لم يبدأ تنفيذ المحركات وقت هذا القرار — فأُزيل ذلك الدافع بدلاً من تصحيحه. يستند هذا القرار إلى الملاءمة المعمارية المُحتَجّ بها في §3-1–3-3 والمصفوفة المُقدَّرة في §4، لا إلى التكلفة الغارقة. :::

4- مصفوفة القرار المُقدَّرة (تقييم الفريق)​

لنقل هذا القرار من حجة سردية إلى شيء يستطيع الفريق تحدّيه نقطة بنقطة، جرى تقييم المقارنة نفسها صراحةً مقابل 12 اهتماماً مرتبطاً بمعمارية هذا البرنامج الفعلية (وحدات القطاعات المكتشَفة عبر بيانات وصفية، تقييد الاستحقاقات المنطقي/المُقاس، بيئات التشغيل المعزولة لكل قطاع) — لا رأياً عاماً حول "Nest مقابل Fastify". المقياس: الدرجة 1 = ملاءمة ضعيفة، 3 = مناسبة، 5 = ملاءمة ممتازة؛ الوزن 1 = ثانوي، 5 = حاسم.

#الاهتمامالوزندرجة NestNest مرجّحةدرجة FastifyFastify مرجّحة
1اكتشاف وحدات القطاع (تسجيل تلقائي عبر مجلد + بيان وصفي)5210525
2تجريد وحدة واحد (بلا ازدواجية بيان ↔ @Module)428520
3بنية نطاق للمحركات المعقّدة (Payment 26 متطلباً، Auth 25 متطلباً)4520312
4التطبيق العابر (الاستحقاقات / RBAC / القياس)5525420
5السهولة وسرعة التطوير (منحنى التعلّم، الشيفرة القياسية، فريق من 8)4312416
6قابلية النقل / الارتباط عبر القطاعات326515
7قابلية اختبار منطق المحرك3412412
8تعقيد التجريد / قابلية التصحيح326515
9النظام البيئي / التوظيف / الإلمام24836
10أداء وقت التشغيل248510
11المرونة المستقبلية / خيارات الخروج326515
12عبء الترقية والصيانة23648
الإجمالي (الحد الأقصى المرجّح = 200)40127174
النسبة من الحد الأقصى63.5%87.0%

تبرير الدرجات (سطر واحد لكل درجة، ليستطيع الفريق تحدّيها):

#لماذا تحصل Nest+Fastify على هذه الدرجةلماذا تحصل Fastify على هذه الدرجة
1تحتاج فحصاً قبل الإقلاع + استيرادات ديناميكية + مصنع DynamicModule — ممكن لكنه يُصارع نموذج وحدات Nest الساكن.يقوم @fastify/autoload باكتشاف المجلدات بسطر واحد.
2البيان وصنف @Module() = مفهوما وحدة يُبقيان متزامنَين.تندمج الإضافة + البيان في مفهوم واحد.
3الوحدات + DI + الطبقات المفروضة = بنية حقيقية لمحركات بـ500 نقطة.قابلة للتحقيق عبر الاتفاقيات، لكن بلا فرض من الإطار.
4الحرّاس + المُعترِضات + DI تصريحية ونظيفة.تعمل الخطّافات + المُزخرِفات جيداً، بربط يدوي أكثر قليلاً.
5كل شيء مُضمَّن، لكن مفاهيم أكثر بالإضافة لمُحمِّل اكتشاف مخصَّص يُكتب يدوياً.مساحة أصغر، أقل للتعلّم، الاكتشاف مجاني.
6يجب أن يكون كل قطاع مستهلِك تطبيق Nest، للأبد.يستطيع أي تطبيق Fastify تسجيل الإضافات — حتى المُغلَّف بـNest.
7أدوات اختبار جيدة، لكن الاختبارات غالباً تحتاج سياق تطبيق Nest.خفيف للتشغيل؛ متعادل إن بقيت أنوية النطاق مستقلة عن الإطار.
8طبقتا إطار (Nest فوق Fastify) بالإضافة لسحر DI يُتتبَّع.رقيق وشفّاف.
9مجتمع أكبر، مجمع توظيف أوسع، دروس أكثر.شائع لكن نظام بيئي أصغر.
10Fastify من الأسفل، نفقات Nest طفيفة لكل طلب.مباشر، بلا طبقة وسيطة.
11صعب التراجع عن Nest عبر كل القطاعات لاحقاً.تستطيع القطاعات تبنّي Nest-on-Fastify محلياً لاحقاً دون مسّ المحركات.
12إصدارات Nest الكبرى + توافق المُوائم + تغييرات DI تحتاج تنسيقاً.أجزاء متحرّكة أقل للترقية.

افتراضان مبنيّان في هذه الدرجات:

  • أنوية نطاق مستقلة عن الإطار. تفترض عدّة درجات (قابلية الاختبار، قابلية النقل، البنية) أن يبقى منطق نطاق المحرك خالياً من استيرادات خاصة بإطار. إذا تسرّبت أساسيات Nest إلى النطاق، تنخفض درجات Nest في #6 و#7 و#11 أكثر.
  • متطلب الاكتشاف عبر مجلد + بيان وصفي حقيقي. يُرجَّح الاهتمامان #1 و#2 عالياً لأن هذا هو نموذج وحدات البرنامج الفعلي. لو تُخلِّي عن الاكتشاف عبر المجلدات لصالح استيرادات ثابتة، لانتقل #1 و#2 نحو Nest — هذه المصفوفة دالة على المعمارية المُختارة بالفعل، لا ترتيباً عاماً للأطر.

قراءة النتيجة: الفجوة (87.0% مقابل 63.5%) مدفوعة بأعلى الاهتمامات وزناً والخاصة بالمعمارية — اكتشاف الوحدات، التطبيق العابر، قابلية النقل، قابلية التصحيح، المرونة المستقبلية — لا بشعبية الإطار العامة (#9، وزن 2، هو الصف الوحيد الذي تتفوّق فيه Nest وهو مُرجَّح منخفضاً عمداً). هذا يعكس §3: تفوز Fastify بالملاءمة لهذه المعمارية المحددة القائمة على البيانات الوصفية، لا بكونها "إطاراً أفضل" بالمطلق.

5- ما غيّرته النقود المُراجَعة في هذا القرار​

النقطة المطروحةمقبولة؟الحسم
المبالغة في القول إن "تغليف Fastify هو حد الاستحقاقات".مقبولةتراجُع وتصحيح في §3-2. هذا أهم تصحيح في هذه المراجعة.
القول إن "Nest لا تستطيع توفير نفس دورة الحياة/الحرّاس" ادّعاء مبالَغ فيه.مقبولةأُعيدت صياغته في §3-1 كقيمة معمارية إضافية غير كافية، لا استحالة تقنية.
عزل الفشل بين المحركات المتشاركة في عملية واحدة غير محدَّد بما يكفي.مقبولةرُفعت لمتطلب من الدرجة الأولى — راجع §6. كانت غائبة كلياً عن التبرير الأصلي.
قراءة Payment لجداول Entitlements مباشرة اقتران مخطط مخفي، لا عقد واضح.مقبولةرُفعت لمتطلب من الدرجة الأولى — راجع §7.
تخاطر لغة "المحركات" بأن تُقرأ خطأً كـ"خدمات قابلة للنشر باستقلالية".مقبولةوُضِّح المصطلح في §8.
بما أن بيئة تشغيل المحرك معزولة لكل قطاع، يجب تأليف المحركات كوحدات NestJS تُوزَّع كحزم، مع Fastify فقط كمُوائم HTTP لـNest من الأسفل.مرفوضةالافتراض (المصدر مشترك، بيئة التشغيل معزولة) كان المعمارية القائمة بالفعل، لا معلومة جديدة — لا يمسّ أي إطار يُؤلِّف شيفرة المحرك نفسها. سيفرض التأليف بـNest أن يكون كل قطاع تطبيق Nest لاستهلاك محرك مشترك (§3-3)، وهو تراجع في قابلية النقل بلا قدرة مكتسَبة في المقابل.
قد يستفيد منطق أعمال قطاع خاص به من بنية Nest مع نمو تعقيد ذلك القطاع.مقبولة، محدودة النطاقتُركت كقرار محلي خاص بكل قطاع — راجع §9. لا تنطبق على كيفية تأليف المحركات المشتركة نفسها.

6- متطلب جديد: عزل فشل وقت التشغيل بين المحركات​

يمنح تشارك عملية Fastify واحدة المحركات الخمسة تركيباً منخفض الكمون، ودورة حياة مشتركة، ونشراً بسيطاً — لكنه يعني أيضاً أن خللاً في محرك واحد قد ينتشر إلى بيئة التشغيل بأكملها إن لم يُصمَّم العزل صراحة. لم يكن هذا محدَّداً سابقاً وهو الآن متطلب غير وظيفي على مستوى البرنامج، مُلزِم لكل المحركات الخمسة بالتساوي:

  • يجب ألا تستطيع Notifications إضعاف أو حجب Authentication.
  • يجب ألا يجعل حجم كتابة Audit أو تراكمها Payment غير متاح.
  • يجب ألا يحجب بطء بوابة الدفع في Payment طلبات API العادية إلى ما لا نهاية (حدود مهلة زمنية مطلوبة على كل استدعاء صادر).
  • يمرّ العمل غير المتزامن لكل محرك عبر طابور المهام المشترك بعزل طابور لكل محرك، لا طابور واحد غير مُميَّز.
  • يجب ألا يمنع فشل الإقلاع في التحقق من بيان محرك واحد إقلاع الأربعة الأخرى.
  • حدود الاستثناءات: يجب التقاط أي خطأ غير مُعالَج داخل إضافة محرك واحد عند حد تلك الإضافة، وتسجيله، وألا يُسقِط العملية المشتركة.

7- متطلب جديد: سياسة صريحة لاقتران قاعدة البيانات بين المحركات​

خيار البرنامج القائم — قاعدة بيانات PostgreSQL مشتركة واحدة، RLS لعزل المستأجرين، كل محرك يملك جداوله الخاصة — قرار معماري أكبر من اختيار الإطار الذي يغطّيه هذا القرار المعماري، ويسمح حالياً لمحرك واحد على الأقل (Payment) بقراءة جداول محرك آخر (tenant_subscriptions الخاص بـEntitlements) مباشرة. تخلق القراءات المباشرة للجداول عقداً مخطط مخفياً لا صريحاً. يتبنّى هذا البرنامج صراحة:

  • أي قراءة جدول عبر المحركات عقد مُرقَّم، لا تفصيل تنفيذ. يُعامَل تغيير مخطط لجدول يقرؤه محرك آخر كمرشّح تغيير كاسر وفق نفس قاعدة الاعتماد المحدَّدة بالفعل في وثيقة متطلبات البرنامج §8 ("يتطلب التغيير الكاسر اعتماد كل قطاع مستهلِك معروف قبل الدمج") — مُوسَّعة هنا لتشمل المحركات المستهلِكة، لا القطاعات المستهلِكة فقط.
  • حيثما أمكن عملياً، تُستبدَل قراءة الجدول المباشرة باستدعاء لدالة النطاق/API الداخلية الخاصة بالمحرك المالك، فتصبح التبعية على عقد، لا مخطط. حيثما أُبقيت قراءة مباشرة لأسباب أداء، يجب توثيقها في وثيقة متطلبات ذلك المحرك كتبعية صريحة، لا تُترَك ضمنية.

8- توضيح المصطلحات​

تعني "المحرك" في وثائق هذا البرنامج نطاقاً/مكوّناً نمطياً مملوكاً باستقلالية ضمن بيئة تشغيل المنصّة المشتركة — لا خدمة قابلة للنشر باستقلالية. ينصّ قسم حزمة التقنيات الخاص بهذا البرنامج بالفعل على أن المحركات "ليست أبداً خدمة مصغّرة قابلة للنشر منفصلة"؛ يجعل هذا القرار المعماري المصطلح صريحاً لكي لا تُقرَأ كلمة "محرك" لاحقاً خطأً على أنها تعني قابلية نشر مستقلة. إذا استُخرِج محرك يوماً فعلياً إلى خدمة قابلة للنشر مستقلة بذاتها (متروك كخيار مستقبلي في القسم 9)، فذلك قرار معماري مميّز ومُسجَّل بشكل منفصل — لا إعادة تفسير للقرار الحالي.

9- متروك مفتوحاً صراحة — غير مُحسوم هنا​

  • يجوز لقطاع ينمو منطق أعماله الخاص تعقيداً كافياً للاستفادة من حدود وحدات مفروضة أن يُغلِّف شيفرته الخاصة بتطبيقه في NestJS-on-Fastify. هذا قرار محلي خاص بكل قطاع، يُطلِقه صعوبة مُلاحَظة (مخالفات متكررة لحدود الوحدات، احتكاك في الإلحاق)، لا يُتبنّى استباقياً. لا يُغيّر هذا كيفية تأليف المحركات المشتركة أو استهلاكها.
  • يجوز استخراج محرك لاحقاً إلى خدمة مستقلة فعلياً منشورة بشكل منفصل إذا بررت متطلبات تشغيلية ذلك (مثل حاجة Payment لتوسّع مستقل أو حد امتثال أكثر صرامة). لا يحسم هذا القرار المعماري ذلك؛ يؤكد فقط أن معمارية اليوم monolith نمطي، لا خمس خدمات، وأن قرار اليوم يُبقي ذلك الاستخراج ممكناً بدلاً من إغلاقه.