معمارية الواجهات الأمامية في المؤسسات: ارسم الحدود أولاً
معمارية الواجهات الأمامية في المؤسسات حدودٌ أولاً: طبقات بعقد استيراد يفرضه الفحص الآلي، ومستودع موحّد أو لا، والواجهات المصغّرة في الغالب لا.

يستورد مكوّن عرض () مخزناً () «لمرة واحدة فقط». وبعد خمس سنوات، صار نصف مجلد ui يعرف الواجهة الخلفية، ولم يعد أحد يستطيع إعادة تنسيق زر دون انتكاسة () في التحويلات. هكذا تشيخ الواجهات الأمامية في المؤسسات: لا بقرار سيئ واحد، بل بحدٍّ لم يفرضه أحد.
المعمارية هي الجزء الذي يعيش أطول من الإطار، لذلك تأتي أولاً في هذه السلسلة. والسؤال ليس أيّ نمط تختار، بل أيّ الخطوط لا يحق لفرقك تجاوزها، وما الذي يمنعها من ذلك.
طبقات بعقد يحدد من يستورد ممّن
قسّم الكود حسب النوع، لا حسب الميزة فقط: domain (نماذج وقواعد خالصة)، و data-access (الكود الوحيد الذي يتحدث إلى الواجهة الخلفية)، و ui (مدخلات ومخرجات فقط)، و feature (يربط الثلاثة)، و app (الهيكل والمسارات). ثم دوّن من يحق له أن يستورد ممّن.
مع فريق واحد يبدو ذلك إجراءً شكلياً. لكنه منذ الفريق الثاني يصبح الشيء الوحيد الذي يُبقي عمل الفرق منفصلاً. والقاعدة المفيدة قصيرة: ui لا يصل أبداً إلى data-access، و domain لا يستورد شيئاً، ولا شيء حديث يستورد الكود القديم ().
القاعدة المكتوبة في صفحة توثيق تتآكل. ضعها في الفحص الآلي.
يتيح لك أن تضع وسماً (tag) على كل مشروع، وأن تحدد أيّ الوسوم يحق لها الاعتماد على أيّها. ويذكر توثيقه السبب في سطر واحد: «إذا استطاعت كلها أن تعتمد على بعضها بحرية، فستعمّ الفوضى، وستصبح مساحة العمل غير قابلة للإدارة.» لا تستخدم Nx؟ أداة «تفرض حدود الوحدات وقواعد الاعتماديات في TypeScript»، لذلك تعمل أيضاً في مشروع CLI عادي.
خطأ الفحص الآلي () هو بيت القصيد. فالمراجِع يتعب في الساعات الأخيرة من أسبوع العمل، أما القاعدة فلا تتعب.
مستودع موحّد أم مستودعات متعددة
يقولها Nx بوضوح: مع الأدوات الحديثة «الاختيار تنظيمي في الغالب». وتنصح الصفحة نفسها بالمستودعات المتعددة () «حين يكون التحكم الصارم في الوصول على مستوى المستودع شرطاً لا تنازل عنه، أو حين تتشارك الفرق القليل من الكود».
في المؤسسات، أحوّل ذلك إلى اختبار. هل تتشارك فرقك نظام تصميم، ونموذجاً للمجال، وقطار إصدارات () واحداً؟ إذن مستودع واحد، والحدود داخله. وهل تطلق منتجات منفصلة إصداراتها وفق جداولها الخاصة، ولا تتشارك شيئاً تقريباً؟ إذن مستودعات منفصلة. كما أن لا يفرض إصداراً واحداً: تُبنى المشاريع من الـ نفسه، لكن «الفرق تتحكم في موعد وصول الوسم إلى الإنتاج».
الواجهات المصغّرة: في الغالب لا
الواجهات المصغّرة () «تطبيقات واجهة أمامية قابلة للتسليم باستقلالية» تُجمع في منتج واحد. والمكسب حقيقي: كل فريق ينشر وحده. والكلفة حقيقية أيضاً. فالمقال نفسه يحذّر من أن «حزم JavaScript المبنية باستقلالية قد تسبب تكرار الاعتماديات المشتركة»، ويسأل هل تملك «النضج التقني والتنظيمي المطلوب».
اختباري: اخترها حين يجب على مؤسسات منفصلة أن تنشر باستقلالية داخل هيكل (shell) واحد. ولا تخترها لإصلاح تطبيق أحادي () فوضوي. فالتطبيق الأحادي بلا حدود يصبح خمسة مستودعات بلا حدود، يُضاف إليها عقد في وقت التشغيل.
الواجهات المصغّرة لا تُصلح حدّاً غائباً. إنها تنقله إلى وقت التشغيل.
نظام التصميم والركن القديم
نظام التصميم () مكتبة كأي مكتبة أخرى، ورموز التصميم فيه هي العقد. تقرأ المكوّنات اللون حسب دوره، لا حسب قيمته الست عشرية، فتصبح إعادة الهوية البصرية تعديلاً في ملف واحد، لا في أربعمئة.
أما الركن القديم فيُعامل بالعكس: مجمّد، ومغلّف، ولا يستورده أي كود جديد. في تطبيق ، «كل عنصر DOM في الصفحة يملكه إطار واحد بالضبط»، وينبغي أن يقول مخطط الاستيرادات الشيء نفسه. السهم يتجه في اتجاه واحد، من القديم إلى الجديد، فيبقى الكود القديم قابلاً للحذف.
دوّن كل عبور
أحياناً يجب أن يتحرك الحدّ. لا بأس في ذلك، بشرط أن يُدوَّن. فسجل القرار المعماري () «يوثّق قراراً معمارياً واحداً ومبرّراته»، وسيحتاج الفريق القادم إلى المبرّرات أكثر من حاجته إلى الكود.
في Atlas Bank
يكتب Atlas Bank، التطبيق التجريبي لهذه السلسلة، العقدَ في eslint.config.mjs. وهذه أسطر ui و feature و legacy:
{
sourceTag: 'type:ui',
onlyDependOnLibsWithTags: ['type:ui', 'type:domain'],
},
{
sourceTag: 'type:feature',
onlyDependOnLibsWithTags: [
'type:feature',
'type:ui',
'type:data-access',
'type:domain',
],
},
{
sourceTag: 'type:legacy',
onlyDependOnLibsWithTags: [
'type:legacy',
'type:data-access',
'type:domain',
],
},يفشل npm run lint:arch حين يستورد ui مخزناً. ويقول sheriff.config.ts الشيء نفسه بلغة Sheriff للفرق التي لا تستخدم Nx. ويحق لشاشة NgModule القديمة في libs/legacy/transfers أن تقرأ المخازن الحديثة، بينما لا يحق لأي مكتبة حديثة أن تستوردها. ويضيف ملف AGENTS.md في المستودع النصف البشري: «عبور حدود مكتبة يتطلب رابطاً إلى ADR في طلب الدمج.» وتعيش الألوان في apps/atlas/src/styles/tokens، ويفشل npm run check:tokens عند وجود لون خام في أي ملف أنماط آخر (ويحتفظ الركن القديم المجمّد بمظهره القديم إلى أن يُحذف).
ما تركته جانباً، ولماذا
- المعمارية النظيفة أو السداسية (hexagonal) بوصفها تسميات. الطبقات أعلاه هي هذا الشكل نفسه. والاسم لا يغيّر قراراً.
- تفاصيل . أدوات تتغير كل عام. قرّر بشأن الواجهات المصغّرة أولاً، ثم تأتي الأداة.
- التطبيق الأحادي مقابل الخدمات المصغّرة (). نقاش يخص الواجهة الخلفية.
- مبادئ . على مستوى الكود، لا على مستوى النظام.
مع بداية الأسبوع
- ارسم مكتباتك والأسهم بينها. كل سهم لم تخطط له قاعدةٌ محتملة.
- أضف قاعدة واحدة إلى الفحص الآلي، تلك التي تؤلم أكثر (غالباً
uiإلىdata-access). دعها تفشل، ثم أصلح كل حالة أو دوّنها. - اكتب أول ADR للعبور الذي لا تستطيع إزالته بعد.
التالي: الجزء الثاني، عن الحالة.
لوكيل البرمجة الخاص بك
قواعد هذا المقال، مكتوبة لوكيل. الصقها في ملف AGENTS.md لديك، أو احفظها مهارةً ()، أو وجّه وكيلك إلى الملف.
# Frontend architecture rules for enterprise apps Source: https://tt-tech.io/blog/frontend-architecture-enterprise-boundaries For Angular and React frontends. Add to AGENTS.md or save as a skill. Your project's own rules win where they differ. ## Rules 1. Split code by type, not only by feature: `domain` (pure models and rules), `data-access` (the only code that talks to the backend), `ui` (inputs and outputs only), `feature` (wires the three) and `app` (shell and routes). Put new code in the layer it belongs to. 2. Keep the "may import" contract: `ui` never imports `data-access`, `domain` imports nothing, and nothing modern imports the legacy code. 3. Enforce boundaries in lint, not in a wiki. On Nx, tag every project and set which tags may depend on which (`onlyDependOnLibsWithTags`). Without Nx, use Sheriff. 4. When the boundary lint fails, let it fail: fix the import, or record the crossing. Never switch the rule off to make the change pass. 5. When a boundary must move, write it down in an Architectural Decision Record (ADR) with the rationale, and reference it in the pull request. 6. Treat the design system as a library under the same rules. Components read colour from tokens by role, never from a raw hex value. 7. Keep the legacy corner frozen and wrapped. No new code imports it. Dependencies point one way, from old to new, so the old code stays deletable. 8. Treat monorepo or polyrepo as an organisational choice. One repository with boundaries inside fits teams that share a design system, a domain model and one release train. Separate repositories fit products that ship on their own schedules and share almost nothing, or when strict repository-level access control is a hard requirement. 9. Do not introduce micro-frontends to fix a messy codebase. They fit when separate organisations must deploy independently into one shell. Fix the missing boundary first. ## Before you say done - Run the project's boundary lint (Nx module boundaries or Sheriff) and fix every new error. - Search the diff for imports from `ui` into `data-access`, from modern code into legacy code, and for raw colour values in stylesheets. - List every boundary crossing in the pull request with its ADR.
المصادر
- Nx, Enforce Module Boundaries, التوثيق
- Sheriff, Introduction, التوثيق
- Nx, Monorepo vs Polyrepo, التوثيق
- Cam Jackson, Micro Frontends, martinfowler.com, 2019
- Angular, UpgradeModule, مرجع الواجهة البرمجية
- منظمة ADR على , Architectural Decision Records (ADRs)