تبدأ معظم نقاشات Frontend Architecture بالأسماء:components, state, Hooks, routes, packages. لكن النقاشات المفيدة تبدأ بفعل واحد: التغيير.
لا تستمد الـ codebase قيمتها من ترتيب صناديقها، بل من قدرة الفريق على تغيير المنتج بأمان وسرعة، مع فهم معقول لما قد يتأثر. والـ architecture هي مجموعة القرارات التي تجعل ذلك ممكنًا.
يغيّر هذا المنظور السؤال الأساسي. بدلًا من «أين ينبغي أن يعيش هذاcomponent؟»، اسأل: «ما الأشياء التي يُرجّح أن تتغيّر معه؟». الإجابة دليل أدق إلىownership, dependencies, APIs, tests، وحتىrendering strategy.
الفكرة المحوريةقرّب الأشياء التي تتغيّر معًا، وضع interfaceواضحة بين الأشياء التي تتغيّر لأسباب مختلفة.
٠١ / الفرضية
الـ Architecture هي استراتيجية لإدارة التغيير.
تخيّل اثنتين codebases لهما بنية المجلدات نفسها. كلتاهما تستخدمfeature modules, TypeScript, a component library, Server Rendering. فريق يشحن التغييرات بثقة، والآخر يحوّل كل تعديل في الأسعار إلى أسبوع من إصلاحregression bugs. مخططاهما متشابهان؛ لكن الـ architecture ليست كذلك.
يختبئ الفرق عادةً في dependency direction ومن يملك القرار. هل تملك Checkout feature القواعد التي تمنحها معناها؟ أم تتوزع القواعد عبرshared layers عامة وglobal store بعيد عن الـ feature؟ لا تجيب شجرة المجلدات عن ذلك، لكن مسار التغيير يجيب.
هذه ليست معادلة حسابية حرفية، بلforcing function تجبرنا على التفكير بوضوح. الـ Architecture الجيدة تُبقي التغيير محليًا، وتجعل أثره قابلًا للملاحظة، وتقلّل عدد الأشخاص أو الوحدات التي يجب أن تتنسّق لإطلاقه.
٠٢ / قبل المجلدات
ارسم خريطة الـ Change Vectors
قبل ما تعيد تنظيم الـ code، دوّن القوى التي تؤثر في المنتج. في معظم أنظمة الـ Frontend ستجد ثلاثة change vectors أساسية:
Product change
القواعد، والتدفقات، والصلاحيات، والـ experiments، وما يُسمح للمستخدم بفعله.
Presentation change
اللغة البصرية، وأنماط التفاعل، والـ accessibility، والـ responsive behavior.
Platform change
الـ APIs، والمصادقة، والـ analytics، والـ rendering، والـ caching، وقيود التشغيل.
أضف الآن change cadence. قد تتبدل قواعد التسعير في كل sprint، بينما يتغيّر نظام الخطوط مرة في السنة. وقد تبقى المصادقة مستقرة حتى يتغير مزوّد الهوية، فتؤثر فجأة في كل شيء تقريبًا. الـ frequency وحده ليس الخطر؛ الخطر هو الـ frequency مضروبًا فيblast radius.
تستحق المناطق ذات high frequency وhigh blast radius حدودًا مدروسة بعناية. أما التفاصيل المحلية قليلة التغيير فلا تحتاجabstraction ceremony. بهذا نتجنبunder-design وover-engineering معًا.
٠٣ / قاعدة الحدود
حدّد الـ boundaries حسب إيقاع التغيير، مش نوع الملف.
تجمع technical layers—components, Hooks, services, utilities—الأشياء المتشابهة شكلًا. أماproduct slices فتجمع الأشياء التي تتغيّر معًا. في التطبيقات التي تتطور باستمرار، الثانية غالبًا هي الـ default الأقوى.
features/
appointments/
api.ts // transport for this capability
model.ts // domain types and rules
queries.ts // cache policy and orchestration
ui/ // feature-specific interface
tests/ // behavior at the boundary
shared/
ui/ // truly product-agnostic primitives
platform/ // auth, telemetry, HTTP clientلا تفيد هذه البنية إلا إذا كانdependency direction حقيقيًا. يمكن لـshared Button أن يعرف حالات الـ focus والـ visual variants؛ لكن لا ينبغي أن يعرف معنى «تأكيد الموعد». الـAppointment feature هي التي تركّب الزر وتملك النص، والـ validation، والـ mutation، والـ analytics event، وحالة النجاح. المعنى يتدفق إلى داخل الـ feature، لا إلى خارجها نحو الـDesign System.
كل عنصر يوضع في shared يحمل ضمنيًاcompatibility promise. لو feature واحدة فقط تستخدمه، خليه local. انقله إلى shared عندما يكشف استخدام حقيقي ثانٍ عن شكله المستقر، لا عندما يوحي الاستخدام الأول باحتمال وجوده.
٠٤ / اختبار الضغط
أربعة اختبارات لأي boundary مفيدة.
- ٠١
Locality test
هل يمكن إنجاز product change عادي داخل feature واحدة غالبًا، أم يتطلب جولة في الـ repository كله؟
- ٠٢
Interface test
هل تستطيع وصف ما يعبر الـ boundary بلغة المنتج؟ «المواعيد المتاحة» contract واضح، أما
miscDataفهو leak. - ٠٣
Replacement test
لو تغيّرت الـ API، أو state library، أو الـ UI treatment، ما حجم الـ unrelated code التي ستلاحظ ذلك؟
- ٠٤
Deletion test
هل يمكن حذف الـ feature بنظافة؟ الـ deletability مقياس لا يأخذ حقه في قياس coupling.
لن ينجح أي system في كل test بشكل كامل. الهدف ليس الـ purity، بل جعل الـ coupling ظاهرًا بما يكفي كي يختاره الفريق بوعي.
٠٥ / في React وNext.js
Blueprint عملي في React وNext.js.
في React application حديث، أطبّق نموذج «التغيير أولًا» عند خمسboundaries. كل boundary يجيب عن سؤال مختلف.
ما الذي يجب أن يملك عنوانًا، ويكون shareable، ويُعمل له render بشكل مستقل؟
ما الذي يمكن حسمه قبل وصول JavaScript إلى الـ browser؟
أي product capability تملك هذا السلوك وهذه المفردات؟
أي visual أو interaction contract استقر بما يكفي لإعادة استخدامه؟
من يملك freshness، وتنظيم البيانات، والـ errors، والـ mutation lifecycle؟
ابدأ بـ Server Components عندما يكون الشغل متعلقًا بالـ data access، أو authorization، أو non-interactive composition. وانتقل إلى Client Component عند أصغرinteraction boundary مفيدة. الهدف ليس مطاردةzero-JavaScript score، بل منعserver concerns وbrowser concernsمن تغيير بعضها بعضًا بدون قصد.
خلّي الـ remote state قريبة من الـ feature التي تفسّرها. الـ Query Cache ليست domain model، والـ Global Store ليس بديلًا عن ownership. اشتق الـ view state حيث تُستهلك، ولا تعمل persist إلا للحالة التي يجب أن تعيش أطول من الـ view.
الـ reuse نتيجة لفكرة مستقرة، مش نقطة بداية لفكرة جديدة.
٠٦ / التطور
طوّر الـ Architecture بدون Rewrite.
المفروض الـ architecture تتحسن من خلال product work. الـ rewrite تطلب من الـ business أن يتوقف بينما يعيد المهندسون إنتاج قيمة الأمس. أماboundary migration فتجعل requirement اليوم تموّل بنية الغد.
- ١
اختار change hotspot واحدة. استند إلى churn، وdefects، وcoordination pain؛ مش مجرد عدم إعجابك بشكل الـ code.
- ٢
سمِّ الـ capability. امنحها product vocabulary وowner واضحًا.
- ٣
عرّف interface ضيقة. ثبّت الـ current behavior بعدد صغير من boundary tests.
- ٤
مرّر الشغل الجديد من خلالها. انقل الـ legacy paths لما تلمسها، وتوقف عن إنشاء dependencies جديدة على الـ legacy shape.
- ٥
احذف الـ bridge. أي temporary adapter محتاج exit condition، وإلا يتحول إلى architecture دائمة.
بعد تغييرين أو ثلاثة، راجع النتيجة. هل تغيّرت files أقل؟ هل احتاج الـ code review إلى context أقل؟ هل بقيت الـ defects محلية؟ تصبح الـ architecture قابلة للقياس عندما نقيّم مسار الشغل الحقيقي، مش أناقة الـ diagram.
٠٧ / خذ هذا معك
Architecture Decision Record قصيرة.
قبل إضافة abstraction جديدة، اكتب خمسة أسطر. هذا القدر من الـ rigor كافٍ لكشف فرضية ضعيفة بدون تحويل القرار إلى paperwork.
CHANGE أي product change متكرر نريد أن نجعله أسهل؟
OWNER أي capability تملك القرار؟
BOUNDARY ما الذي يُسمح له بالعبور، وبأي language؟
TRADE-OFF ما الذي سيصبح أصعب بسبب الاختيار؟
SIGNAL ما الـ evidence التي ستدفعنا لمراجعة القرار؟
أفضل Frontend Architecture مش الأذكى غالبًا. هي التي تسمح للشخص التالي أن يجد القرار، ويفهم حدوده، ويغيّره بدون ما يحتاج خريطة للـ system كله.
صمّم للـ change التي تراها.
واترك مساحة للـ change التي لا تراها.