واجهتنا تطلب الكون: كيف أنقذنا نمط BFF من جحيم الـ API الواحد؟

بتذكرها زي كأنها مبارح. الساعة كانت قربت على ١١ بالليل، وأنا براجع شوية Pull Requests. فجأة، بيوصلني مسج على سلاك من “خليل”، الشب الشاطر اللي ماسك عنا تطوير واجهة الموبايل. المسج كان عبارة عن صورة لقط أسود معصب، وتحتها جملة: “يا أبو عمر، مش منطق الحكي هاد!”.

ضحكت ودخلت أشوف شو القصة. لقيت خليل وفريقه متغلبين مع API أساسي عنا. بدهم يعرضوا صفحة بسيطة في التطبيق: اسم المستخدم، صورته، وآخر ٣ طلبات إله. المشكلة إنه الـ API اللي برجع بيانات المستخدم، برجع “كل إشي”. يعني حرفيًا، من تاريخ ميلاده لآخر مرة غيّر فيها كلمة السر، مع قائمة بكل أصدقائه وعناوين الشحن المحفوظة… ملف JSON حجمه بيكبر وبيكبر، وخليل بس بده ٣ حقول منه!

وقتها رمى خليل جملته اللي بعدها بتطن في أذني: “يا عمي، الواجهة الأمامية بتطلب القمر، والـ API بعطينا المجرة كلها! وإحنا بدنا نقعد نفصفصها على الموبايل؟”. كان معه حق مية بالمية. هاي الليلة كانت بداية رحلتنا مع نمط غيّر طريقة تفكيرنا وبناء أنظمتنا: نمط الواجهة الخلفية للواجهة الأمامية (Backend for Frontend)، أو زي ما بنحب نسميه “المنقذ”.

ما هو الجحيم الذي كنا نعيشه؟ (مشكلة الـ API الواحد)

قبل ما نحكي عن الحل، خلينا نفهم أصل المشكلة. في كثير من الأنظمة، خصوصًا اللي بتبدأ بسيطة وبعدين بتكبر، بنلاقي حالنا أمام واجهة برمجة تطبيقات (API) واحدة وعامة. هاي الـ API مصممة عشان تخدم “الكل”: موقع الويب، تطبيق الموبايل (أندرويد و iOS)، ويمكن حتى أنظمة داخلية أو شركاء خارجيين.

المشكلة إنه “مقاس واحد لا يناسب الجميع” (One-size-fits-all) هو وهم في عالم البرمجيات. كل واجهة إلها متطلباتها الخاصة:

  • تطبيق الموبايل: يحتاج بيانات قليلة ومختصرة لتقليل استهلاك باقة الإنترنت وتسريع التحميل على شبكات بطيئة.
  • تطبيق الويب (ديسكتوب): يمكنه التعامل مع بيانات أكثر تعقيدًا، وقد يحتاج إلى معلومات مجمّعة لعرض لوحات تحكم غنية.
  • تطبيقات الطرف الثالث (Third-party): قد تحتاج إلى صيغة بيانات معينة (مثل XML بدل JSON) أو مستوى مختلف من الصلاحيات.

لما نحاول نخدم كل هدول العملاء بـ API واحد، بتصير الكارثة. فريق الواجهة الأمامية بصير يقضي وقته في فلترة وتجميع وتشكيل البيانات بدل ما يركز على تجربة المستخدم. وهذا بالضبط اللي كان صاير مع خليل وفريقه.

“كان فريق الواجهة الأمامية يكتب منطقًا برمجيًا معقدًا فقط لـ ‘تنظيف’ البيانات القادمة من الخلفية. كانوا فعليًا يبنون واجهة خلفية صغيرة داخل الواجهة الأمامية، وهذا كابوس صيانة.”

المنقذ: نمط الواجهة الخلفية للواجهة الأمامية (BFF)

هنا يأتي دور البطل في قصتنا: نمط الـ Backend for Frontend (BFF). الفكرة بسيطة بشكل عبقري: بدلًا من وجود API خلفي واحد وعام، نقوم بإنشاء واجهة خلفية صغيرة ومخصصة لكل واجهة أمامية.

تخيلها بهذا الشكل:

  • عندك Web App BFF: واجهة خلفية مخصصة لخدمة تطبيق الويب.
  • عندك Mobile App BFF: واجهة خلفية أخرى مخصصة لخدمة تطبيق الموبايل.
  • وعندك Public API BFF: واجهة خلفية لخدمة الشركاء الخارجيين.

هذه الـ BFFs ليست قواعد بيانات أو أنظمة كاملة. هي طبقة رقيقة (thin layer) تقع بين الواجهة الأمامية والخدمات الخلفية الحقيقية (سواء كانت Microservices أو Monolith). وظيفتها الأساسية هي أن تكون “مترجمًا” و”منسقًا”.

ماذا يفعل الـ BFF بالضبط؟

الـ BFF هو بمثابة “المساعد الشخصي” للواجهة الأمامية. الواجهة الأمامية بتطلب منه طلب واضح ومحدد، وهو بروح على “السوق الكبير” (الخدمات الخلفية) وبجيب الطلبات بالضبط زي ما انطلبت منه، وبحطها في كيس واحد مرتب. من أهم وظائفه:

  1. تجميع البيانات (Aggregation): بدل ما تطبيق الموبايل يعمل طلب لخدمة المستخدمين وطلب ثاني لخدمة الطلبات، هو بيعمل طلب واحد للـ Mobile BFF. والـ BFF بدوره بكلم الخدمتين وبجمع البيانات وبرجعها في رد واحد بسيط.
  2. تخصيص البيانات (Tailoring): الـ BFF بياخد البيانات الضخمة من الخدمات الخلفية وبفلترها وبختار منها فقط ما تحتاجه الواجهة الأمامية. لا زيادة ولا نقصان.
  3. ترجمة البروتوكولات (Protocol Translation): ممكن تكون الخدمات الخلفية بتستخدم بروتوكولات مختلفة (مثل gRPC أو GraphQL)، لكن الـ BFF بيقدر يوحدها ويرجع للواجهة الأمامية بروتوكول REST API بسيط ومفهوم.
  4. معالجة الأخطاء المخصصة (Custom Error Handling): بدل ما يرجع للواجهة الأمامية خطأ غامض مثل `500 Internal Server Error` من خدمة ما، الـ BFF بيقدر يمسك الخطأ ويرجع رسالة واضحة ومفهومة للمستخدم النهائي مثل “عفوًا، لم نتمكن من جلب طلباتك الأخيرة. الرجاء المحاولة لاحقًا”.

مثال عملي: لننقذ خليل وفريقه!

نرجع لمشكلة خليل: عرض اسم المستخدم وصورته وآخر ٣ طلبات. في العالم القديم (بدون BFF)، كان تطبيق الموبايل سيقوم بالآتي:

  1. إرسال طلب `GET /api/users/{userId}` للحصول على بيانات المستخدم (ويرجع ملف JSON ضخم).
  2. بعد الحصول على الرد، يرسل طلبًا آخر `GET /api/orders?userId={userId}` للحصول على كل طلبات المستخدم.
  3. يقوم التطبيق بفلترة الرد الأول لأخذ الاسم والصورة، وفلترة الرد الثاني لأخذ آخر ٣ طلبات، ثم يدمجها للعرض. (كل هذا على جهاز المستخدم!).

الآن، مع وجود الـ Mobile BFF، أصبح الأمر مختلفًا تمامًا. يقوم تطبيق الموبايل بعمل طلب واحد فقط:

GET /api/mobile/profile-summary

وهذا ما يحدث خلف الكواليس في الـ Mobile BFF (مكتوب بـ Node.js/Express كمثال):


// This is the code for our Mobile BFF server
import express from 'express';
import axios from 'axios'; // To make HTTP requests to other services

const app = express();
const PORT = 3001;

// The microservice URLs
const USER_SERVICE_URL = 'http://user-service:8080';
const ORDER_SERVICE_URL = 'http://order-service:8081';

app.get('/api/mobile/profile-summary/:userId', async (req, res) => {
    const { userId } = req.params;

    try {
        // 1. Make two parallel requests to the downstream services
        const userPromise = axios.get(`${USER_SERVICE_URL}/users/${userId}`);
        const ordersPromise = axios.get(`${ORDER_SERVICE_URL}/orders?userId=${userId}&limit=3&sort=date:desc`);

        // Wait for both promises to resolve
        const [userResponse, ordersResponse] = await Promise.all([userPromise, ordersPromise]);

        // 2. Extract only the data we need
        const fullUserData = userResponse.data;
        const fullOrdersData = ordersResponse.data;

        // 3. Create a tailored, simple response object for the mobile app
        const mobileProfileSummary = {
            username: fullUserData.profile.name,
            avatarUrl: fullUserData.profile.avatar,
            recentOrders: fullOrdersData.map(order => ({
                orderId: order.id,
                status: order.status,
                total: order.summary.totalAmount,
            }))
        };

        // 4. Send the clean, aggregated data back to the mobile client
        res.json(mobileProfileSummary);

    } catch (error) {
        // 5. Provide a clean error message
        console.error("Error fetching profile summary:", error);
        res.status(500).json({ message: "حدث خطأ أثناء جلب بيانات الملف الشخصي." });
    }
});

app.listen(PORT, () => {
    console.log(`Mobile BFF is running on port ${PORT}`);
});

لاحظ الفرق! تطبيق الموبايل الآن يتلقى رد JSON بسيط ونظيف وجاهز للعرض مباشرة:


{
  "username": "أبو عمر",
  "avatarUrl": "https://example.com/avatars/abu-omar.jpg",
  "recentOrders": [
    { "orderId": "xyz", "status": "تم التوصيل", "total": 150.00 },
    { "orderId": "abc", "status": "تم التوصيل", "total": 75.50 },
    { "orderId": "efg", "status": "في الطريق", "total": 210.00 }
  ]
}

لا معالجة معقدة على العميل، استهلاك أقل للبيانات، وأداء أسرع. خليل وفريقه صاروا يقدروا يناموا بالليل.

نصيحة من أبو عمر

لما تبني BFF، فكر فيه كأنه جزء من فريق الواجهة الأمامية. في كثير من الأحيان، يكون فريق الواجهة الأمامية هو نفسه من يطور ويصون الـ BFF الخاص به (باستخدام تقنيات مثل Node.js أو Next.js API Routes). هذا يعطيهم استقلالية وسرعة هائلة، لأنهم لم يعودوا بحاجة للانتظار في “طابور” فريق الواجهة الخلفية لعمل أي تعديل بسيط.

متى تستخدم BFF؟ ومتى تبتعد عنه؟

مثل أي نمط معماري، الـ BFF ليس حلاً سحريًا لكل المشاكل. “ما تطلقش النار على ذبانة بمدفع”.

استخدمه عندما…

  • لديك عدة واجهات أمامية مختلفة (ويب، موبايل، ساعة ذكية، إلخ) بمتطلبات بيانات متباينة.
  • تريد تسريع وتيرة التطوير للواجهات الأمامية ومنحها استقلالية.
  • تواجه مشاكل أداء على الواجهات الأمامية بسبب حجم أو شكل البيانات القادمة من الخلفية.
  • تريد إضافة طبقة أمان إضافية بإخفاء تعقيدات البنية التحتية الخلفية.

ابتعد عنه (أو فكر مرتين) عندما…

  • لديك تطبيق بسيط بواجهة أمامية واحدة فقط و API بسيط يخدمها بشكل جيد.
  • فريقك صغير جدًا ولا يملك الموارد لتشغيل وصيانة خدمة إضافية. (مع أن تقنيات الـ Serverless مثل AWS Lambda أو Vercel Functions سهّلت هذا الأمر كثيرًا).
  • لا تريد إضافة أي تعقيد على البنية التحتية. كل خدمة إضافية تعني المزيد من المراقبة والنشر والاهتمام.

الخلاصة والزبدة 💡

نمط الـ BFF كان بمثابة طوق نجاة لنا. لقد حرر فرق الواجهات الأمامية من قيود الـ API العام، وسمح لهم بالابتكار والتركيز على ما يهم حقًا: بناء تجربة مستخدم رائعة. لقد حوّل شكوى “الواجهة تطلب القمر والـ API يعطينا المجرة” إلى شراكة متناغمة، حيث أصبح لكل واجهة “مساعدها الشخصي” الذي يفهمها ويحضر لها ما تريده بالضبط.

نصيحتي الأخيرة لك: إذا وجدت نفسك أو فريقك في نفس موقف خليل، وكنتم تقضون وقتًا أطول في “مصارعة” الـ API بدلًا من بناء الميزات، فربما حان الوقت لتقديم الـ BFF إلى نظامكم. ابدأوا بشكل صغير، ربما بواجهة أمامية واحدة أو بنقطة نهاية (endpoint) حرجة واحدة، وقيسوا الفرق. أراهن أنكم لن تندموا. 👍

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

خوارزميات

كان البحث عن ‘الأماكن القريبة’ يقتل الأداء: كيف أنقذتنا ‘شجرة الكواد’ (Quadtree) من جحيم البحث الخطي؟

قصة حقيقية من قلب المعركة البرمجية، كيف انتقلنا من تطبيق بطيء ينهار تحت ضغط البحث عن الأماكن القريبة إلى أداء صاروخي بفضل خوارزمية "شجرة الكواد"....

14 مايو، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

كان تطبيقنا سجنًا رقميًا: كيف أنقذتنا ‘إمكانية الوصول’ (Accessibility) من جحيم استبعاد المستخدمين؟

أشارككم قصة حقيقية من مسيرتي كمطور، قصة كيف تحول تطبيق كنا نفخر به إلى سجن رقمي استبعد فئة كبيرة من المستخدمين. هذه المقالة ليست مجرد...

14 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت خوادمنا خاملة 90% من الوقت: كيف أنقذتنا ‘الحوسبة بدون خوادم’ (Serverless) من جحيم التكاليف المهدرة؟

أشارككم قصة حقيقية من تجربتي كمطور، كيف كنا ندفع مئات الدولارات على خوادم شبه نائمة، وكيف كانت معمارية "الحوسبة بدون خوادم" (Serverless) طوق النجاة الذي...

14 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت إجاباتي في المقابلات عشوائية: كيف أنقذتني منهجية STAR من جحيم أسئلة “حدثنا عن موقف…”؟

هل تجد نفسك تائهًا ومشتتًا عند الإجابة على أسئلة المقابلات السلوكية؟ في هذه المقالة، أشاركك تجربتي الشخصية مع منهجية STAR، الأداة التي حولت إجاباتي الفوضوية...

14 مايو، 2026 قراءة المزيد
البودكاست