خدماتنا تتحدث بلغات مختلفة: كيف أنقذ نمط BFF (الواجهة الخلفية للواجهات الأمامية) مشروعنا من فوضى الـ API؟

أذكر ذلك المساء جيداً. كنا على بعد أسابيع قليلة من إطلاق منتجنا الجديد، وهو عبارة عن منصة متكاملة لها تطبيق ويب وتطبيقات للموبايل (iOS وأندرويد). القهوة كانت رفيقتنا الدائمة، وأصوات نقر الكيبورد لا تهدأ. فريق الويب كان شبه منتهٍ من عمله، والتطبيق كان يعمل بسلاسة على شاشاتهم الكبيرة واتصالهم السريع بالإنترنت في مكاتبنا.

لكن في الجهة الأخرى من المكتب، كانت الأجواء متوترة. فريق الموبايل، يا حرام، كان يعاني الأمرين. التطبيق بطيء، يستهلك البطارية بشكل جنوني، وكل شاشة تحتاج لعدة طلبات (requests) للـ API حتى تعرض البيانات. سمعت أحدهم يتمتم بلهجة فيها قهر: “هاي الـ APIs مش إلنا، هاي معمولة للويب وبس!”.

عقدنا اجتماعاً طارئاً. الأجواء كانت مشحونة. فريق الموبايل يشتكي من أن الخدمات المصغرة (Microservices) ترسل بيانات ضخمة لا يحتاجونها (Over-fetching)، أو أحياناً يضطرون لعمل ٥ طلبات مختلفة فقط لعرض صفحة البروفايل. وفريق الباكند يدافع عن نفسه قائلاً: “هذه هي الخدمة، لا يمكننا عمل Endpoint مخصص لكل شاشة ولكل تطبيق!”.

شعرت أننا في برج بابل، كل فريق يتحدث لغة مختلفة والكل يلوم الآخر. وقفت أمام اللوح الأبيض، ورسمت مربعاً واحداً، وقلت لهم: “يا جماعة الخير، الحل مش عندكم ولا عندهم. الحل في النص. خلينا نبني جسر، أو مُترجم… خلينا نحكي عن الـ BFF”.

المشكلة: برج بابل في عالم الخدمات المصغرة

قبل أن نغوص في الحل، دعونا نفصّل المشكلة التي كنا (وربما أنتم أيضاً) نمر بها. عندما تتبنى معمارية الخدمات المصغرة، تحصل على الكثير من المرونة والقوة، لكنك تواجه تحدياً جديداً: كيف ستتواصل كل هذه الواجهات الأمامية المختلفة (ويب، موبايل، تلفاز ذكي…) مع هذا الجيش من الخدمات المصغرة؟

هنا تظهر عدة مشاكل جوهرية:

واجهة API واحدة لا تناسب الجميع

محاولة بناء API “عامة” تخدم كل المنصات هي وصفة للفشل. فمتطلبات تطبيق الويب تختلف جذرياً عن تطبيق الموبايل:

  • تطبيق الويب: يعمل على اتصال سريع ومستقر، شاشة كبيرة، قدرة معالجة عالية. لا يمانع من استقبال كم كبير من البيانات.
  • تطبيق الموبايل: يعمل على اتصال متقلب (4G, 3G, Wi-Fi)، شاشة صغيرة، بطارية محدودة. كل بايت إضافي يتم تحميله هو استنزاف لباقة الإنترنت والبطارية.

عندما تعطي تطبيق الموبايل نفس الـ API المصممة للويب، فأنت تجبره على التعامل مع حمولة لم يصمم لها.

مشكلة الـ “ثرثرة” (Chattiness)

تخيل أن صفحة الملف الشخصي في تطبيقك تحتاج لعرض: معلومات المستخدم، آخر 5 مقالات له، وعدد متابعيه. في عالم الخدمات المصغرة، هذه البيانات قد تكون موزعة على 3 خدمات مختلفة:

  1. user-service: للحصول على اسم المستخدم وصورته.
  2. posts-service: للحصول على مقالاته.
  3. followers-service: للحصول على عدد المتابعين.

بدون وجود وسيط، سيضطر تطبيق الموبايل لعمل 3 طلبات شبكة منفصلة. هذا يسمى “Chatty API”، وهو قاتل لأداء تطبيقات الموبايل بسبب تأخير الشبكة (Network Latency) لكل طلب.

معضلة الإفراط والنقص في جلب البيانات (Over/Under-fetching)

  • Over-fetching (الإفراط): خدمة المستخدمين قد ترجع كائن JSON يحتوي على 50 حقلاً (تاريخ الميلاد، عنوان التسليم، آخر IP، إلخ)، بينما تطبيق الموبايل يحتاج فقط لاسم المستخدم وصورته الشخصية. كل هذه البيانات الإضافية هي هدر للموارد.
  • Under-fetching (النقص): نقطة النهاية (Endpoint) الخاصة بالمقالات ترجع فقط معرفات المستخدمين الذين كتبوها، مما يجبر التطبيق على عمل طلب إضافي لكل مقال لجلب اسم وصورة الكاتب.

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

هنا يأتي دور البطل في قصتنا: نمط Backend for Frontend أو اختصاراً BFF. الفكرة بسيطة بشكل عبقري، لكنها قوية جداً.

شو هو الـ BFF يا أبو عمر؟

ببساطة، الـ BFF هو طبقة (Layer) أو خدمة (Service) إضافية تقع بين الواجهة الأمامية والخدمات المصغرة. لكن الجزء الأهم هو أن هذا الـ BFF يكون مخصصاً لواجهة أمامية محددة.

يعني، يكون لدينا:

  • BFF لتطبيق الويب (Web BFF).
  • BFF لتطبيق الأندرويد (Android BFF).
  • BFF لتطبيق الـ iOS (iOS BFF).
  • … وهكذا.

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

الـ BFF هو الواجهة الخلفية التي يكتبها مطورو الواجهة الأمامية (أو على الأقل يشاركون في تصميمها) لتلبية احتياجاتهم الخاصة.

كيف يعمل هذا السحر؟

دعنا نرجع لمثال صفحة الملف الشخصي التي تحتاج 3 طلبات:

  1. تطبيق الموبايل الآن يقوم بعمل طلب واحد فقط، بسيط وواضح، إلى الـ Mobile BFF. مثلاً: GET /bff/mobile/user-profile/123.
  2. الـ Mobile BFF يستقبل هذا الطلب.
  3. يقوم هو بالتحدث مع الخدمات المصغرة الثلاثة (user-service, posts-service, followers-service) في نفس الوقت (بشكل متوازي).
  4. عندما تصله الردود من كل الخدمات، يقوم بتجميعها، فلترتها، وتشكيلها بالطريقة التي يريدها تطبيق الموبايل بالضبط. مثلاً، يأخذ الاسم والصورة من خدمة المستخدمين، وعناوين أول 5 مقالات فقط من خدمة المقالات، ورقم المتابعين من خدمة المتابعين.
  5. يرسل رداً واحداً (Single Response) منظماً وخفيفاً إلى تطبيق الموبايل.

النتيجة؟ طلب شبكة واحد فقط من العميل، استجابة سريعة، وبيانات محسّنة خصيصاً له. مشكلة الـ Chattiness والـ Over-fetching تم حلها بضربة واحدة.

مثال عملي: خلينا نوسّخ إيدينا شوي

لنفترض أننا نستخدم Node.js و Express لبناء الـ BFF الخاص بنا (وهو خيار شائع جداً لأن JavaScript مألوفة لمطوري الواجهة الأمامية).

الطريقة القديمة (بدون BFF)

كان كود الواجهة الأمامية (Frontend) سيبدو هكذا تقريباً:

// Frontend Client Code (e.g., in React Native)
async function fetchUserProfile(userId) {
  try {
    // Request 1: Get user details
    const userPromise = fetch(`https://api.example.com/users/${userId}`);
    // Request 2: Get user posts
    const postsPromise = fetch(`https://api.example.com/posts?userId=${userId}`);

    const [userResponse, postsResponse] = await Promise.all([userPromise, postsPromise]);

    const userData = await userResponse.json();
    const postsData = await postsResponse.json();

    // Combine and process data on the client... what a headache!
    const profileData = {
      name: userData.profile.full_name, // complex nested object
      avatar: userData.profile.pictures.small_avatar_url,
      posts: postsData.items.slice(0, 5) // filter and slice
    };
    
    return profileData;

  } catch (error) {
    console.error("Failed to fetch profile data:", error);
  }
}

الطريقة الجديدة (مع BFF)

أولاً، كود الواجهة الأمامية يصبح نظيفاً جداً:

// Frontend Client Code (much cleaner!)
async function fetchUserProfile(userId) {
  // Just one single, beautiful call
  const response = await fetch(`https://bff.example.com/mobile/profile/${userId}`);
  const profileData = await response.json();
  return profileData;
}

ثانياً، السحر كله يحدث في الـ BFF (كود جهة الخادم):

// Mobile BFF Code (Node.js / Express)
const express = require('express');
const axios = require('axios'); // For making HTTP requests
const app = express();

const USER_SERVICE_URL = 'http://user-service.internal';
const POST_SERVICE_URL = 'http://post-service.internal';

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

  try {
    // Make requests to internal microservices in parallel
    const userPromise = axios.get(`${USER_SERVICE_URL}/users/${userId}`);
    const postsPromise = axios.get(`${POST_SERVICE_URL}/posts?userId=${userId}`);

    const [userResponse, postsResponse] = await Promise.all([userPromise, postsPromise]);

    // Now, aggregate and transform the data into the *exact* shape the mobile app wants
    const mobileProfile = {
      userName: userResponse.data.name,
      profilePicture: userResponse.data.avatar_url,
      latestPosts: postsResponse.data
        .map(post => ({ // We only take title and date
          title: post.title,
          date: post.created_at
        }))
        .slice(0, 5) // And only the top 5
    };

    res.json(mobileProfile);

  } catch (error) {
    // Handle errors, maybe return a simplified error object
    res.status(500).json({ message: 'Failed to retrieve profile data.' });
  }
});

app.listen(3000, () => console.log('Mobile BFF is running!'));

لاحظ الفرق! التعقيد انتقل من جهاز العميل المحدود إلى خادم قوي وقريب من الخدمات الأخرى. الواجهة الأمامية أصبحت “غبية” (وهذا أمر جيد)، وكل ما عليها فعله هو عرض البيانات التي تصلها جاهزة ومُفصّلة على مقاسها.

الـ BFF مقابل بوابة الـ API (API Gateway): هل هما نفس الشيء؟

هذا سؤال مهم جداً ويسبب الكثير من اللغط. الجواب المختصر: لا، ليسا نفس الشيء، ولكنهما يكملان بعضهما البعض بشكل رائع.

  • بوابة الـ API (API Gateway): هي نقطة دخول واحدة لـ كل الطلبات من كل العملاء. وظيفتها الأساسية هي أمور عرضية (Cross-cutting concerns) مثل التوجيه (Routing)، المصادقة (Authentication)، تحديد معدل الطلبات (Rate Limiting)، والتحقق من الصلاحيات. هي بمثابة “حارس الأمن” على باب العمارة.
  • الـ BFF: هو طبقة متخصصة تخدم عميلاً واحداً. وظيفتها الأساسية هي تجميع وتكييف البيانات (Aggregation & Adaptation). هو بمثابة “المساعد الشخصي” الذي يجهز لك طلباتك داخل العمارة بعد أن سمح لك حارس الأمن بالدخول.

أفضل المعمارايات: استخدم الاثنين معاً!

في الأنظمة الكبيرة، النمط الأقوى هو أن يعمل الاثنان معاً:

العميل (Client) -> بوابة API (API Gateway) -> الواجهة الخلفية للواجهة الأمامية (BFF) -> الخدمات المصغرة (Microservices)

بهذه الطريقة، تتولى بوابة الـ API الأمور المشتركة (كالمصادقة)، ثم توجه الطلب إلى الـ BFF المناسب، الذي بدوره يقوم بمهمة تجميع البيانات وتقديمها للعميل.

نصائح من قلب الميدان

بعد تطبيق هذا النمط في عدة مشاريع، خرجت ببعض الدروس التي أحب أن أشاركها معكم:

  1. من يملك الـ BFF؟ هذا أهم سؤال. أفضل إجابة هي: الفريق الذي يستهلكه. يعني فريق الموبايل يجب أن يملك ويطور الـ Mobile BFF. هذا يعطيهم القوة والاستقلالية لتصميم الـ API التي يحتاجونها بالضبط، دون الحاجة للانتظار أو التفاوض مع فرق الباكند.
  2. لا تضع منطق العمل (Business Logic) في الـ BFF: الـ BFF هو مُجمّع ومُنسّق، وليس دماغاً. منطق العمل الأساسي (مثل كيفية حساب سعر منتج أو التحقق من رصيد) يجب أن يبقى في الخدمات المصغرة الأساسية. إذا بدأ الـ BFF الخاص بك يحتوي على منطق عمل معقد، فهو في طريقه ليصبح وحشاً متضخماً (Monolith).
  3. ابدأ صغيراً: لست بحاجة لبناء BFF لكل شيء من اليوم الأول. ابدأ بالنقاط الأكثر إيلاماً في تطبيقك، تلك الشاشات البطيئة والمعقدة، وقم ببناء BFF لها. ثم توسع تدريجياً.
  4. انتبه من تكرار الكود: إذا كان لديك BFF للـ iOS وآخر للـ Android، قد تجد أن 80% من الكود متشابه. هنا يمكنك التفكير في بناء BFF واحد للموبايل (Mobile BFF) يخدم كلا التطبيقين، مع بعض المنطق الشرطي البسيط إذا لزم الأمر.

الخلاصة: لمّ الشمل بعد الفوضى

في قصتنا، كان بناء BFF هو ما أنقذ الموقف. فرق الموبايل أصبحت أكثر إنتاجية وسعادة. التطبيقات أصبحت أسرع وأكثر استجابة. والأهم، أن لغة الحوار بين الفرق تغيرت من اللوم إلى التعاون. الـ BFF لم يكن مجرد حل تقني، بل كان جسراً للتواصل بين فرق العمل المختلفة.

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

فلا تخف من إضافة هذه “الطبقة الإضافية”، فقد تكون هي ما يحتاجه مشروعك لينطلق من الفوضى إلى النظام. 😉

أبو عمر

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

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

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

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

آخر المدونات

ذكاء اصطناعي

كان نموذجنا اللغوي يهلوس بلا توقف: كيف أنقذنا ‘التوليد المعزز بالاسترجاع’ (RAG) من جحيم الإجابات الخاطئة؟

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

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

كان تطبيقنا حصناً منيعاً أمام المكفوفين: كيف أنقذتنا سمات ARIA من جحيم الإقصاء الرقمي؟

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

12 مايو، 2026 قراءة المزيد
برمجة وقواعد بيانات

كانت صفحاتنا تستغرق دهراً للتحميل: كيف أنقذنا ‘التحميل المسبق’ (Eager Loading) من جحيم مشكلة N+1؟

أشارككم قصة حقيقية عن كيفية تحول أحد مشاريعنا من البطء الشديد إلى السرعة الفائقة. اكتشفوا معنا مشكلة N+1 الخبيثة، وكيف كان التحميل المسبق (Eager Loading)...

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

كانت إجاباتي في المقابلات عشوائية: كيف أنقذني إطار STAR من جحيم الأسئلة السلوكية؟

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

12 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

قاعدة بياناتنا المونوليثية كانت قنبلة موقوتة: كيف أنقذنا ‘التقسيم’ (Sharding) من جحيم عنق الزجاجة؟

كانت ليلة لا تُنسى، ليلة انفجرت فيها قنبلة قاعدة بياناتنا المونوليثية. في هذه المقالة، أسرد لكم يا جماعة كيف انتقلنا من حافة الانهيار إلى نظام...

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