وداعاً لـ TypeScript : كيف تبني Backend متكاملاً لتطبيق Flutter باستخدام Dart Cloud Functions

هل سبق لك كمطور Flutter أن شعرت بالإحباط عند محاولة بناء جزء الـ Backend لتطبيقك؟ تقضي أيامك في كتابة أكواد Dart معبرة، آمنة من الـ null، وذات أنواع قوية في الواجهة الأمامية. نماذج البيانات (Models) لديك نظيفة، وسلاسل async/await تُقرأ بسلاسة، ونظام الأنواع (Type System) الخاص بك يلتقط فئات كاملة من الأخطاء قبل حتى أن يتم تشغيلها. ثم تفتح تبويباً جديداً لكتابة Cloud Function، وتجد نفسك فجأة في ملف TypeScript، تعيد تعريف نفس نموذج User الذي حددته للتو في Dart، وتحافظ يدوياً على تزامن الإصدارين، وتصحح خطأ cannot read property of undefined الذي كان مترجم Dart سيلتقطه في أجزاء من الثانية.

هذا الاحتكاك لم يكن مجرد إزعاج بسيط. لقد كان عبئاً هيكلياً أساسياً على مطوري Flutter الذين أرادوا امتلاك (Full Stack) تطويرهم. كنت مضطراً للحفاظ على قاعدتي أكواد بلغتين مختلفتين، بنموذجين مختلفين للتزامن، ونظامين للأنواع، وبيئتين مختلفتين للحزم (Package Ecosystems)، ومجموعتين من الأدوات. كل تغيير في شكل البيانات المشتركة كان يتطلب تعديلين. كل خطأ في عقد البيانات (Data Contract) بين العميل والخادم كان يتطلب منك التبديل الذهني بين اللغات لتتبعه. غالباً ما كانت الفرق التي تبني تطبيقات Flutter مع Backend يعتمد على Firebase توظف مطوري Backend خصيصاً لأن العبء المعرفي لـ JavaScript كان كبيراً جداً على فريق يركز على تطوير تطبيقات الجوال.

هذا يتغير الآن. Firebase أعلنت عن دعم تجريبي لـ Dart، وإلى جانبه، حزمة Dart Admin SDK تجريبية تتيح لك التفاعل مع Firestore، وAuthentication، وCloud Storage، وخدمات Firebase الأخرى من داخل كود الدالة (Function). يمكنك الآن كتابة الـ Backend الخاص بك بنفس اللغة التي تستخدمها في الواجهة الأمامية، ومشاركة نماذج البيانات ومنطق التحقق (Validation Logic) في حزمة Dart مشتركة يستوردها كلا الجانبين، ونشر كود الخادم الخاص بك باستخدام نفس أداة firebase CLI التي تستخدمها بالفعل. حلم Dart Full Stack ، الذي كان المطورون يطلبونه لسنوات، أصبح حقيقة رسمية.

هذا المقال هو دليلك الهندسي الشامل ل Dart Full Stack . سنغطي كيفية عمل Dart Cloud Functions ، وكيف تختلف عن دوال Node.js في البنية والنشر، وكيف تربط حزمة Admin SDK دالتك بخدمات Firebase ، وكيف تشارك المنطق بين تطبيق Flutter والـ Backend الخاص بك باستخدام حزمة Dart مشتركة، وكيف تستدعي دوالك من Flutter، وكل القيود الحالية التي تحتاج إلى معرفتها قبل الاعتماد على ميزة تجريبية في بيئات الإنتاج.

ما هو Dart Cloud Functions ؟ (تعريف مختصر وعملي)

Dart Cloud Functions هي بيئة حوسبة بلا خادم (Serverless Compute Platform) تتيح لك كتابة ونشر منطق Backend بلغة Dart، والذي يتم تشغيله استجابة لأحداث معينة داخل Firebase أو Google Cloud. تعني كلمة
بلا خادم (Serverless) أنك تكتب دالة (Function)، تنشرها، وتتولى Google إدارة كل شيء آخر: الخوادم، التوسع (Scaling)، موازنة التحميل (Load Balancing)، تحديثات نظام التشغيل، والتوافر. أنت تدفع فقط مقابل وقت الحوسبة الذي تستخدمه دوالك فعلياً، ويتم قياسه بأجزاء من الثانية، وتتوسع دوالك تلقائياً من صفر طلب إلى ملايين دون أي تهيئة للبنية التحتية من جانبك.

لماذا تهتم بـ Dart Cloud Functions؟ (المشكلة التي يحلها)

Dart Cloud Functions ما الذي يتغير فعلياً

القيمة المقترحة لـ Dart Cloud Functions واضحة ومباشرة. بدونها، إضافة منطق Backend لتطبيق Flutter كان يعني إما تشغيل خادمك الخاص (مكلف، معقد الإدارة) أو حشو منطق الأعمال (Business Logic) داخل العميل (غير آمن، أصعب في التغيير دون تحديث المتجر). تمنحك Cloud Functions طبقة Backend خفيفة الوزن، آمنة، قابلة للتوسع، يمكنك تحديثها بشكل مستقل عن تطبيقك، ويمكنها التحدث إلى كل خدمة من خدمات Firebase بامتيازات مرتفعة لا ينبغي أن يمتلكها العميل أبداً.

قبل دعم Dart، كانت خياراتك لكتابة Cloud Functions هي JavaScript، TypeScript، Python، Java، Go، وRuby. بالنسبة لمطوري Flutter، كل ذلك كان يعني التبديل بين السياقات (Context-Switching) بعيداً عن Dart، وتعلم بيئة وأدوات لغة جديدة، وتكرار المنطق المشترك بين العميل والخادم. الآن أصبحت Dart ضمن هذه القائمة، ولأن تطبيق Flutter الخاص بك هو بالفعل Dart، فإن الآثار المترتبة على ذلك عميقة.

التغيير الواضح هو اللغة. ستكتب ملفات .dart بدلاً من ملفات .ts أو .py. لكن التغيير الأعمق يتعلق بـ الكود المشترك (Shared Code).

في بنية TypeScript + Flutter، يوجد نموذج User الخاص بك مرتين. إصدار واحد في TypeScript على الخادم يحدد شكل مستندات Firestore وما ترجعه الدالة. وإصدار واحد في Dart على العميل يحدد كيفية تحليل تطبيق Flutter وعرض بيانات المستخدم. عندما يتغير حقل، تقوم بتحديث كليهما. عندما ينسى المطور تحديث كليهما، يولد خطأ. غالباً ما يكون هذا الخطأ غير مرئي في مرحلة التطوير لأن الخادم والعميل عادة ما يتم بناؤهما واختبارهما بشكل منفصل، ويظهر فقط في اختبار التكامل (Integration Testing) أو في الإنتاج.

في بنية Dart الكاملة (Full-Stack Dart)، يوجد نموذج User مرة واحدة، في حزمة Dart مشتركة يستوردها كل من الدالة وتطبيق Flutter. قم بتغييره في مكان واحد وينعكس التحديث فوراً على كلا الجانبين. يفرض محلل Dart (Dart Analyzer) أن كلا الجانبين يستخدمان النوع بشكل صحيح. إعادة تسمية حقل هي عملية إعادة هيكلة (Refactor) تقوم بتشغيلها مرة واحدة، حيث يقوم بيئة التطوير المتكاملة (IDE) بإعادة التسمية عبر قاعدة الكود بأكملها في وقت واحد، ويتحقق المترجم (Compiler) من النتيجة.

تعتبر Dart لغة مجمعة مسبقاً (Ahead-of-Time (AOT) Compiled Language)، مما يعني أنها تُترجم إلى كود ثنائي أصلي قبل تشغيلها بدلاً من تفسيرها في وقت التشغيل. هذه الخاصية لها تأثير مباشر على إحدى أكثر المشكلات التي يتم مناقشتها في الدوال بلا خادم: البدء البارد (Cold Starts).

يحدث البدء البارد عندما تكون دالتك في وضع الخمول ويصل طلب جديد. تحتاج المنصة إلى تشغيل مثيل جديد، وإذا كان ذلك يتطلب تحميل وقت تشغيل ثقيل (كما هو الحال في Node.js) أو آلة افتراضية (كما هو الحال في Java)، فإن الطلب الأول بعد فترة من عدم النشاط يمكن أن يستغرق عدة ثوانٍ. في المقابل، تُترجم دالة Dart إلى ثنائي أصلي بدون عبء وقت تشغيل. يكون وقت البدء البارد لدالة Dart أقل بكثير من دوال Node.js أو Python المماثلة، مما يجعلها أكثر ملاءمة لأعباء العمل التي تكون فيها زمن الاستجابة (Latency) في الطلب الأول مهمة.

تعكس عملية النشر هذه البنية. عندما تنشر دالة Dart، لا تقوم أداة Firebase CLI بتحميل الكود المصدري الخاص بك ليتم تجميعه في السحابة بالطريقة التي تعمل بها عمليات نشر Node.js. بل تقوم بتجميع كود Dart الخاص بك إلى ثنائي أصلي على جهاز التطوير الخاص بك، ثم تقوم بتحميل هذا الثنائي مباشرة إلى Cloud Run.

كيف تبدأ؟ (خطوات عملية)

للبدء في استخدام Dart Cloud Functions، تحتاج إلى التأكد من توفر بعض المتطلبات الأساسية وتثبيت الأدوات اللازمة. هذه الخطوات ستضعك على المسار الصحيح:

المتطلبات الأساسية:

  1. إتقان Flutter و Dart: يجب أن تكون مرتاحاً لكتابة تطبيقات Dart متعددة الملفات، والعمل مع async/await و Future، وفهم نظام أمان الـ null (Null Safety) في Dart، وإدارة الحزم باستخدام pub. الخبرة في بناء تطبيقات Flutter أمر متوقع لأن الأمثلة الشاملة ستستدعي الدوال من عميل Flutter.
  2. أساسيات Firebase: يجب أن تكون قد استخدمت Firebase من قبل: أنشأت مشروعاً في Firebase Console، وربطته بتطبيق Flutter باستخدام FlutterFire CLI، ومن الناحية المثالية استخدمت خدمة Firebase واحدة على الأقل مثل Firestore أو Authentication. لا تحتاج إلى خبرة سابقة في Cloud Functions، على الرغم من أن الإلمام بمفهوم الدوال بلا خادم سيساعد.
  3. الراحة مع سطر الأوامر (Command Line): تتم عملية سير عمل Dart Cloud Functions بالكامل في الطرفية (Terminal). تحتاج إلى أن تكون مرتاحاً لتشغيل الأوامر، وقراءة مخرجات الطرفية، والتنقل في نظام الملفات الخاص بك من سطر الأوامر.
  4. الوعي بخطة الفوترة (Billing Plan): يتطلب نشر Cloud Functions من أي نوع إلى الإنتاج أن يكون مشروع Firebase الخاص بك على خطة Blaze (الدفع حسب الاستخدام). تتيح لك Firebase Local Emulator Suite تطوير واختبار الدوال دون حساب فوترة، لذا يمكنك متابعة معظم هذا الدليل محلياً دون تكلفة. ومع ذلك، كن على دراية بأن النشر يتطلب خطة Blaze.

الأدوات التي يجب أن تكون جاهزة:

تأكد من تثبيت ما يلي وإمكانية الوصول إليه من الطرفية قبل البدء:

  • Flutter SDK 3.x أو أعلى (والذي يتضمن Dart SDK 3.x).
  • Firebase CLI الإصدار 15.15.0 أو أعلى (تحقق من الإصدار باستخدام firebase --version؛ قم بالتحديث باستخدام npm install -g firebase-tools).
  • Node.js 18 أو أعلى (مطلوب بواسطة Firebase CLI، وليس بواسطة كود Dart الخاص بك).
  • محرر أكواد مع إضافة Dart (VS Code مع إضافة Dart، أو Android Studio).
  • مشروع Firebase تم إنشاؤه في Firebase Console.

الحزم التي يستخدمها هذا الدليل:

سيتضمن ملف pubspec.yaml الخاص بدليل الدوال الخاص بك ما يلي:

dependencies:
  firebase_functions: ^0.1.0
  google_cloud_firestore: ^0.1.0

firebase_functions هي حزمة Dart الأساسية التي توفر fireUp، وواجهات برمجة التطبيقات (APIs) للتسجيل لـ onRequest و onCall، والأنواع المستخدمة في كود الدالة الخاص بك. google_cloud_firestore هي حزمة Dart Firestore SDK المستقلة المستخدمة حصرياً على جانب الخادم داخل Cloud Functions الخاصة بك. إنها ليست نفس حزمة cloud_firestore التي تستخدمها في تطبيق Flutter الخاص بك. كلاهما يتحدثان إلى Firestore، لكنهما مكتبتان مختلفتان مصممتان لبيئات مختلفة: واحدة لعميل Flutter يعمل تحت قواعد أمان Firebase (Firebase Security Rules)، والأخرى لعملية من جانب الخادم تعمل بامتيازات إدارية كاملة.

حزمتك المشتركة (التي سيتم تغطيتها بالتفصيل لاحقاً) لن تحتوي على أي تبعيات Firebase. سيستمر ملف pubspec.yaml الخاص بتطبيق Flutter الخاص بك في استخدام حزم firebase_core و cloud_firestore وحزم FlutterFire القياسية الأخرى التي يستخدمها بالفعل.

ملاحظة هامة حول الحالة التجريبية لهذه الميزة: كل ما ورد في هذا الدليل يعتمد على دعم Dart التجريبي الذي تم الإعلان عنه في Google Cloud Next 2026. تعني كلمة “تجريبي” أن واجهة برمجة التطبيقات (API) قد تتغير دون إشعار، وبعض الميزات المتاحة في دوال Node.js ليست متاحة بعد في Dart، ولا تعرض Firebase Console دوال Dart بعد. يمكنك عرضها وإدارتها من خلال صفحة دوال Cloud Run في Google Cloud Console بدلاً من ذلك. هذا مجال جديد حقاً، والفريق يعمل بنشاط على تطويره. سيحدد الدليل بوضوح كل قيد عند مواجهته حتى تعرف دائماً بالضبط أين تقع الحدود.

مثال تطبيقي كامل

لنقم ببناء مثال بسيط يوضح كيفية عمل Dart Cloud Functions مع Firebase Admin SDK. سنقوم بإنشاء دالة تستقبل اسماً، ثم تحفظه في Firestore، وتعيد رسالة ترحيب.

1. تهيئة مشروع Firebase Functions جديد

أولاً، تأكد من أنك في مجلد مشروعك الرئيسي، ثم قم بتهيئة مشروع Firebase Functions جديد يدعم Dart:

firebase init functions

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

2. إضافة التبعيات (Dependencies)

انتقل إلى مجلد functions وقم بتعديل ملف pubspec.yaml لإضافة تبعيات firebase_functions و google_cloud_firestore:

# functions/pubspec.yaml
name: functions
description: A Cloud Functions for Firebase project written in Dart.
version: 1.0.0
publish_to: 'none'

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  firebase_functions: ^0.1.0
  google_cloud_firestore: ^0.1.0

dev_dependencies:
  lints: ^2.0.0
  test: ^1.21.0

بعد حفظ التغييرات، قم بتشغيل pub get لتنزيل التبعيات:

pub get

3. كتابة دالة Cloud Function بسيطة

الآن، قم بإنشاء ملف جديد داخل functions/lib (مثلاً functions/lib/src/hello_world.dart) واكتب الدالة التالية:

// functions/lib/src/hello_world.dart
import 'package:firebase_functions/firebase_functions.dart';
import 'package:google_cloud_firestore/cloud_firestore.dart';

void helloWorld(RequestContext context) {
  functions.https.onCall((request) async {
    final data = request.data as Map<String, dynamic>;
    final name = data['name'] as String?;

    if (name == null || name.isEmpty) {
      throw HttpsError(HttpsErrorCode.invalidArgument, 'Name is required.');
    }

    final firestore = Firestore.instance;
    await firestore.collection('greetings').add({
      'name': name,
      'timestamp': FieldValue.serverTimestamp(),
    });

    return {'message': 'Hello, $name! Your greeting has been saved.'};
  });
}

شرح الكود:

  • نستورد firebase_functions للتعامل مع دوال Firebase و google_cloud_firestore للتفاعل مع Firestore. Firestore.instance يمنحنا وصولاً إدارياً كاملاً لقاعدة البيانات.
  • الدالة helloWorld هي نقطة الدخول لدالتنا. نستخدم functions.https.onCall لإنشاء دالة قابلة للاستدعاء من العميل.
  • نستقبل البيانات من الطلب ونتحقق من وجود حقل name. إذا كان مفقوداً، نُرجع خطأ HttpsError.
  • نستخدم Firestore.instance لحفظ الاسم والوقت في مجموعة greetings في Firestore.
  • نُرجع رسالة ترحيب تحتوي على الاسم الذي تم إرساله.

4. تسجيل الدالة

لجعل الدالة قابلة للنشر، يجب تسجيلها في ملف functions/lib/main.dart:

// functions/lib/main.dart
import 'package:firebase_functions/firebase_functions.dart';
import 'package:functions/src/hello_world.dart'; // استورد ملف الدالة الخاص بك

void main() {
  helloWorld(functions.requestContext()); // سجل الدالة هنا
}

شرح الكود:

  • نستورد ملف الدالة hello_world.dart الذي أنشأناه للتو.
  • في الدالة main، نقوم باستدعاء helloWorld مع functions.requestContext() لتسجيلها كدالة قابلة للنشر.

5. النشر والاختبار

لنشر الدالة، تأكد من أنك في مجلد مشروع Firebase الرئيسي (المجلد الذي يحتوي على firebase.json) ثم قم بتشغيل:

firebase deploy --only functions

بعد النشر الناجح، ستظهر لك رسالة تحتوي على اسم الدالة. يمكنك الآن استدعاء هذه الدالة من تطبيق Flutter الخاص بك.

6. استدعاء الدالة من Flutter

في تطبيق Flutter الخاص بك، تأكد من إضافة حزمة cloud_functions إلى pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.x.x
  cloud_functions: ^4.x.x # أضف هذه الحزمة

ثم قم بتشغيل flutter pub get.

الآن، يمكنك استدعاء الدالة في كود Flutter الخاص بك:

// flutter_app/lib/main.dart (مثال)
import 'package:flutter/material.dart';
import 'package:cloud_functions/cloud_functions.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Dart Cloud Functions Example')),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              try {
                final result = await FirebaseFunctions.instance
                    .httpsCallable('helloWorld') // اسم الدالة التي نشرتها
                    .call({'name': 'Flutter Developer'});
                print(result.data['message']); // Hello, Flutter Developer! Your greeting has been saved.
              } on FirebaseFunctionsException catch (e) {
                print('Failed to call function: ${e.code} - ${e.message}');
              }
            },
            child: Text('Call Dart Function'),
          ),
        ),
      ),
    );
  }
}

شرح الكود:

  • نستخدم FirebaseFunctions.instance.httpsCallable('helloWorld') للحصول على مرجع لدالتنا المنشورة.
  • نستدعي الدالة باستخدام call ونمرر البيانات كـ Map.
  • نتعامل مع الاستجابة أو الأخطاء المحتملة.

نصائح ومزالق يجب تجنبها

  1. التعامل مع الحالة التجريبية: تذكر دائماً أن دعم Dart Cloud Functions لا يزال في مرحلة تجريبية. هذا يعني أن واجهة برمجة التطبيقات (API) قد تتغير، وقد تكون هناك قيود على الميزات مقارنة بدوال Node.js. لا تعتمد عليها في أعباء عمل الإنتاج الحرجة دون فهم كامل للمخاطر والقيود الحالية.
  2. إدارة الأخطاء (Error Handling): استخدم HttpsError لإرجاع أخطاء مفهومة للعميل. تجنب إرجاع استثناءات عامة قد تكشف تفاصيل حساسة عن الـ Backend الخاص بك.
  3. التحقق من صحة البيانات (Data Validation): دائماً قم بالتحقق من صحة البيانات الواردة إلى دوالك. لا تثق أبداً ببيانات العميل. هذا يمنع الثغرات الأمنية ويضمن عمل دالتك بشكل صحيح.
  4. الاستفادة من الكود المشترك: استثمر الوقت في إنشاء حزمة Dart مشتركة لنماذج البيانات والمنطق المشترك. هذا هو أحد أكبر فوائد استخدام Dart Cloud Functions ويقلل بشكل كبير من التكرار والأخطاء.
  5. مراقبة التكاليف: على الرغم من أن Cloud Functions فعالة من حيث التكلفة، إلا أن سوء التصميم (مثل الحلقات اللانهائية أو الاستعلامات غير الفعالة لقاعدة البيانات) يمكن أن يؤدي إلى تكاليف غير متوقعة. راقب استخدامك في Google Cloud Console.
  6. اختبار الدوال محلياً: استخدم Firebase Local Emulator Suite لاختبار دوالك محلياً قبل النشر. هذا يوفر الوقت ويقلل من تكاليف النشر والاختبار.

الخلاصة والرأي

لقد كان ظهور Dart Cloud Functions بمثابة نقطة تحول لمطوري Flutter. إن القدرة على بناء (Full Stack) بلغة واحدة – Dart – يزيل قدراً هائلاً من الاحتكاك المعرفي والتعقيد الذي كان يعاني منه المطورون سابقاً. إن مشاركة نماذج البيانات والمنطق بين الواجهة الأمامية والخلفية ليست مجرد ميزة، بل هي تغيير جذري في طريقة تفكيرنا في تطوير تطبيقات Flutter ذات الـ Backend.

في رأيي، على الرغم من أن الميزة لا تزال تجريبية، إلا أنها تستحق الاستكشاف الجاد من قبل أي فريق Flutter جاد في بناء تطبيقات قوية وقابلة للتوسع. الفوائد من حيث تبسيط سير العمل، وتقليل الأخطاء، وتحسين تجربة المطور تفوق بكثير القيود الحالية. متى تستخدمها؟ إذا كنت تبدأ مشروع Flutter جديداً يتطلب Backend، أو إذا كنت تبحث عن تبسيط Backend الحالي المبني على Node.js وترغب في توحيد لغتك. متى تتجنبها؟ في أعباء العمل الحرجة للإنتاج التي لا يمكنها تحمل أي تغييرات محتملة في واجهة برمجة التطبيقات (API) أو قيود الميزات، حتى تصبح الميزة مستقرة تماماً.

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

روابط مهمة:

firebase_functions 0.6.0 

الفصل العاشر – Data & backend : استخدام Firebase في Flutter

اعجبك المقال : شاركه الآن
احمد علي
احمد علي

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

المقالات: 218

اترك ردّاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *