تصميم نظام Push Notifications للتطبيقات باستخدام RabbitMQ وFirebase

تصميم نظام Push Notifications للتطبيقات باستخدام RabbitMQ وFirebase

في هذا الدليل العملي سنتعلّم خطوة بخطوة كيفية تصميم نظام Push Notifications للتطبيقات (موبايل أو ويب) بالاعتماد على RabbitMQ كوسيط للرسائل، وربطه مع Firebase Cloud Messaging (FCM) لإرسال الإشعارات الفعلية إلى أجهزة المستخدمين.

هذا المقال مكمّل لمجموعة شروحات افهم صح حول تصميم أنظمة الإشعارات وقابلية التوسع باستخدام Message Queues. يمكنك الرجوع أيضاً إلى:

الهدف هو بناء نظام مرن، قابل للتوسع، وسهل الصيانة، يفصل منطق إنشاء الإشعارات عن منطق إرسالها الفعلي عن طريق Firebase، باستخدام بنية قائمة على الرسائل (Message-based Architecture).

لماذا نستخدم RabbitMQ مع Firebase في نظام Push Notifications؟

خدمة Firebase Cloud Messaging توفّر واجهة API لإرسال الإشعارات إلى الأجهزة، لكن الاعتماد مباشرةً عليها من داخل الكود التطبيق الأساسي (Monolith أو Microservices) يخلق عدّة مشاكل:

  • ربط قوي (Tight Coupling) بين منطق أعمال التطبيق (Business Logic) ومنطق الإرسال.
  • صعوبة التوسّع في حالة عدد ضخم من الإشعارات.
  • تأثير عمليات الإرسال البطيئة على زمن استجابة الـ API الرئيسي.
  • إدارة أسوأ لعمليات الفشل وإعادة المحاولة (Retries) و Dead Letter Queues.

هنا يأتي دور RabbitMQ كوسيط رسائل:

  • استقبال رسائل الإشعارات من خدمات مختلفة (Microservices أو Backend واحد) بشكل غير متزامن (Asynchronous).
  • تمرير هذه الرسائل إلى Notification Worker متخصص في التعامل مع Firebase.
  • إمكان توزيع الحمل على عدة Consumers بسهولة لزيادة القدرة على معالجة عدد ضخم من الإشعارات.
  • إدارة أفضل للأخطاء عبر Dead Letter Queue و Retry Logic.

بهذا يكون لدينا خط أنابيب (Pipeline) واضح: التطبيق / الخدمة → RabbitMQ → Notification Service → Firebase → جهاز المستخدم.

نظرة معمارية: كيف يبدو نظام rabbitmq push notifications firebase؟

قبل الدخول في التفاصيل التقنية، لنرسم المعمارية العامة للنظام الذي نريد بناءه:

  1. Client App: تطبيق موبايل (Android/iOS) أو تطبيق ويب يحصل على FCM Token لكل مستخدم ويقوم بإرساله إلى السيرفر.
  2. Backend API: يستقبل طلب إرسال الإشعار (مثلاً: عند تسجيل مستخدم جديد، أو وجود طلب جديد في متجر إلكتروني).
  3. RabbitMQ: يحتوي على Exchange وQueue مخصصة للإشعارات.
  4. Notification Service / Worker: تطبيق مستقل (مثلاً مكتوب بـ Python أو Node.js) يقوم باستهلاك الرسائل من RabbitMQ، ثم الاتصال بواجهة FCM API.
  5. Firebase Cloud Messaging: خدمة إرسال الإشعارات الفعلية إلى الأجهزة عبر الـ Token.

بهذا التصميم نستفيد من نمط Event-Driven / Message-Driven Architecture، ونفصل مسؤوليات النظام بشكل واضح.

المتطلبات الأساسية لبناء النظام

1. إعداد مشروع Firebase والحصول على بيانات FCM

لبدء استخدام Firebase Cloud Messaging، نحتاج:

  • إنشاء مشروع على Firebase Console.
  • إضافة تطبيق Android و/أو iOS و/أو Web للمشروع.
  • الحصول على Server Key / Legacy Server Key أو Service Account JSON لاستخدامه مع FCM HTTP v1 API.
  • تجهيز الكود في تطبيق الموبايل أو الويب للحصول على Device Token (Registration Token) من FCM.

2. إعداد RabbitMQ

يمكن تشغيل RabbitMQ محلياً أو على خادم إنتاجي (Production Server):

  • تنصيب RabbitMQ أو تشغيله عبر Docker.
  • تفعيل Management Plugin لمراقبة الـ Queues.
  • إنشاء Exchange وQueue خاصة بالإشعارات، مثل:
    • Exchange: notifications.exchange (نوعه غالباً direct أو topic).
    • Queue: notifications.push.firebase.
    • Routing Key: notifications.push.firebase.

3. خدمة إرسال الإشعارات (Notification Worker)

هذه الخدمة هي المسؤولة عن:

  • الاتصال بـ RabbitMQ كـ Consumer.
  • قراءة الرسائل من notifications.push.firebase.
  • تحويل الرسالة إلى طلب HTTP نحو FCM API.
  • التعامل مع الاستجابة، تسجيل النتائج، ومعالجة الأخطاء.

يمكن بناء هذه الخدمة بأي لغة مفضلة لديك (Python، Node.js، Go، إلخ). إذا كنت تستخدم Python فيمكنك الاستفادة من الأدوات التي شرحناها في دمج RabbitMQ مع FastAPI: بناء نظام إشعارات عالي الأداء.

تصميم نموذج رسالة الإشعار داخل RabbitMQ

أحد أهم عناصر نجاح نظام rabbitmq push notifications firebase هو تصميم Payload الرسالة التي سيتم وضعها في الـ Queue. يجب أن تكون:

  • واضحة وتمثّل ما يحتاجه Firebase لإرسال الإشعار.
  • قابلة للتطوير عند إضافة أنواع جديدة من الإشعارات.
  • مهيّأة لدعم لغات مختلفة أو قنوات مختلفة لاحقاً (مثل Email أو SMS).

مثال لرسالة JSON يمكن وضعها في Queue:

{
  "notification_id": "123456",
  "user_id": 789,
  "device_tokens": [
    "fcm_token_1",
    "fcm_token_2"
  ],
  "title": "طلب جديد",
  "body": "تم استلام طلبك بنجاح",
  "data": {
    "order_id": 555,
    "type": "order_created"
  },
  "priority": "high",
  "created_at": "2024-04-01T10:30:00Z",
  "retries": 0
}

المهم أن تحتوي الرسالة على:

  • device_tokens: مجموعة من Tokens (يمكن أن يكون واحداً) تخص المستخدم أو عدّة أجهزة لنفس المستخدم.
  • title وbody: محتوى الإشعار الظاهر.
  • data: بيانات إضافية (Custom Data) يحتاجها التطبيق عند فتح الإشعار.
  • priority: أولوية الإشعار (مثل high أو normal).
  • retries: عدد مرات المحاولة السابقة، يفيد في منطق إعادة الإرسال.

مسار العمل (Flow) من التطبيق إلى Firebase عبر RabbitMQ

الخطوة 1: التطبيق يرسل طلب إشعار إلى Backend

مثلاً في Backend API (باستخدام FastAPI أو Django أو أي إطار آخر) يكون لدينا Endpoint مثل:

POST /api/notifications/send

يستقبل بيانات مثل:

{
  "user_id": 789,
  "title": "طلب جديد",
  "body": "تم استلام طلبك بنجاح",
  "data": {
    "order_id": 555,
    "type": "order_created"
  }
}

بدلاً من أن يتصل الـ Backend مباشرةً مع FCM، يقوم بـ:

  1. البحث عن device_tokens الخاصة بهذا المستخدم من قاعدة البيانات.
  2. بناء Payload الرسالة بالشكل المتفق عليه.
  3. إرسال الرسالة إلى RabbitMQ Exchange باستخدام Routing Key محددة.

الخطوة 2: RabbitMQ يوجّه الرسالة إلى Queue الإشعارات

بناءً على إعدادات الـ Exchange وBinding، يتم توجيه الرسالة نحو:

Queue: notifications.push.firebase

من الآن فصاعداً، الـ Backend أنهى مهمته سريعاً، وعاد يستجيب للعميل (Client) دون انتظار تنفيذ الإرسال الفعلي للإشعار.

الخطوة 3: Notification Worker يستهلك الرسالة ويرسلها إلى Firebase

الـ Worker يعمل بشكل مستمر:

  1. يستقبل الرسالة من الـ Queue.
  2. يحوّلها إلى طلب HTTP نحو FCM API (إما Legacy API أو HTTP v1).
  3. يتعامل مع النتيجة:
    • في حال النجاح: تأكيد الرسالة (ACK) وحذفها من الـ Queue.
    • في حال فشل قابل لإعادة المحاولة (Network Error/Timeout): زيادة retries وإعادة النشر أو ترك الرسالة ليتم إعادة استهلاكها.
    • في حال فشل دائم (Invalid Token, Unauthorized): تسجيل الخطأ وتحديث بيانات المستخدم (مثلاً حذف Token غير صالح)، ثم ACK للرسالة.

التعامل مع FCM: نموذج طلب إرسال إشعار

من أجل دمج rabbitmq push notifications firebase بشكل فعّال، ستحتاج إلى معرفة شكل طلب FCM. لنأخذ مثالاً مبسطاً باستخدام HTTP v1 API (المُفضَّل حالياً):

POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send
Authorization: Bearer <ACCESS_TOKEN>
Content-Type: application/json

{
  "message": {
    "token": "fcm_token_1",
    "notification": {
      "title": "طلب جديد",
      "body": "تم استلام طلبك بنجاح"
    },
    "data": {
      "order_id": "555",
      "type": "order_created"
    },
    "android": {
      "priority": "HIGH"
    },
    "apns": {
      "headers": {
        "apns-priority": "10"
      }
    }
  }
}

في حال أردت إرسال إشعار لأكثر من Token، يمكنك إما:

  • إرسال طلب منفصل لكل Token داخل الـ Worker.
  • أو استخدام Topic Messaging في FCM (ربط المستخدمين بموضوع/Topic معيّن وإرسال رسالة واحدة للـ Topic).

استراتيجيات Retry و Dead Letter Queue مع RabbitMQ و FCM

في الواقع العملي، الاتصال مع Firebase قد يفشل لعدة أسباب:

  • Timeout أو مشكلة مؤقتة في الشبكة.
  • تجاوز حدّ معدل الإرسال (Rate Limiting).
  • مشاكل في Authentication (Token غير صالح أو منتهي).
  • Token جهاز غير صالح أو منتهي.

من المهم تصميم منطق Retry جيد. يمكن الاستفادة هنا من مفاهيم Dead Letter Queue التي شرحناها في مقال كيفية التعامل مع الرسائل الفاشلة في Kafka وRabbitMQ باستخدام Dead Letter Queue.

نموذج بسيط لمنطق Retry

  1. الحالات التي يمكن إعادة المحاولة فيها (Transient Errors):
    • أخطاء شبكة، Timeout.
    • Error Codes متعلقة بحالات مؤقتة.
  2. الحالات التي لا يمكن إعادة المحاولة فيها (Permanent Errors):
    • InvalidRegistration، NotRegistered (Token غير صالح).
    • Authentication Error (إذا لم يتم حلّه سريعاً، ينقل إلى DLQ).

استراتيجية عملية:

  • كل رسالة تحمل حقل retries (محاولات سابقة).
  • إذا فشل الإرسال بسبب خطأ مؤقت وزادت عدد المحاولات عن حد معيّن (مثلاً 5)، تُنقل الرسالة إلى Dead Letter Queue مثل notifications.push.firebase.dlq.
  • يمكن بناء Dashboard لمراقبة الـ DLQ وتحليل الأخطاء الحرجة.

أفضل الممارسات عند تصميم نظام rabbitmq push notifications firebase

1. فصل أنواع الإشعارات في Queues مختلفة

يمكنك تقسيم الإشعارات حسب النوع أو الأهمية:

  • notifications.push.firebase.high لإشعارات الحرجة (مثل معاملات مالية).
  • notifications.push.firebase.normal للإشعارات العادية (مثل تحديثات عامة).

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

2. استخدام قوالب (Templates) للإشعارات

بدلاً من إرسال نصوص جاهزة من الـ Backend في كل مرة، يمكنك:

  • تعريف قوالب للإشعارات (Notification Templates) يتم حفظها في قاعدة البيانات.
  • إرسال نوع الإشعار والـ Data فقط عبر RabbitMQ.
  • قيام الـ Worker بتحميل القالب وبناء النص النهائي (مع دعم تعدد اللغات).

3. تسجيل الأحداث (Logging) والتتبّع (Tracing)

من المهم جداً توثيق:

  • متى تم إنشاء الإشعار؟
  • متى تم إرساله إلى RabbitMQ؟
  • متى حاول الـ Worker إرساله إلى FCM؟
  • هل نجح؟ ما هي استجابة FCM؟

هذا يساعدك على:

  • فهم مشاكل الإرسال.
  • تقديم دعم أفضل للمستخدمين.
  • تحسين أداء النظام وحجم الـ Queues.

4. الحذر من إرسال حمل ضخم دفعة واحدة

عند إرسال حمل ضخم من الإشعارات (مثلاً Broadcast لجميع المستخدمين)، يجب:

  • تقسيم الإرسال إلى دفعات (Batches) صغيرة.
  • ضبط معدل النشر في RabbitMQ حتى لا تزدحم الـ Queues بشكل مبالغ فيه.
  • مراقبة معدل أخطاء FCM، خاصةً في حالات Rate Limiting.

مثال مبسط لسير العمل باستخدام Python وRabbitMQ وFirebase

للتوضيح فقط (المثال ليس كود إنتاجي كامل، لكنه يوضّح الفكرة العامة):

جزء الـ Backend: نشر رسالة في RabbitMQ

import json
import pika

def publish_notification(message: dict):
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost')
    )
    channel = connection.channel()

    exchange = 'notifications.exchange'
    routing_key = 'notifications.push.firebase'

    channel.exchange_declare(exchange=exchange, exchange_type='direct', durable=True)
    channel.queue_declare(queue='notifications.push.firebase', durable=True)
    channel.queue_bind(queue='notifications.push.firebase', exchange=exchange, routing_key=routing_key)

    channel.basic_publish(
        exchange=exchange,
        routing_key=routing_key,
        body=json.dumps(message),
        properties=pika.BasicProperties(
            delivery_mode=2  # make message persistent
        )
    )
    connection.close()

جزء الـ Worker: استهلاك الرسالة وإرسالها إلى FCM

import json
import pika
import requests

FCM_ENDPOINT = "https://fcm.googleapis.com/fcm/send"
FCM_SERVER_KEY = "YOUR_FCM_SERVER_KEY"

def send_to_fcm(payload):
    headers = {
        "Authorization": f"key={FCM_SERVER_KEY}",
        "Content-Type": "application/json"
    }
    response = requests.post(FCM_ENDPOINT, headers=headers, json=payload, timeout=5)
    response.raise_for_status()
    return response.json()

def callback(ch, method, properties, body):
    message = json.loads(body)
    try:
        for token in message["device_tokens"]:
            fcm_payload = {
                "to": token,
                "notification": {
                    "title": message["title"],
                    "body": message["body"]
                },
                "data": message.get("data", {})
            }
            send_to_fcm(fcm_payload)
        ch.basic_ack(delivery_tag=method.delivery_tag)
    except Exception as e:
        # في نظام حقيقي يجب هنا تطبيق منطق Retry / DLQ
        print("Error:", e)
        ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)

def start_worker():
    connection = pika.BlockingConnection(
        pika.ConnectionParameters(host='localhost')
    )
    channel = connection.channel()
    channel.basic_qos(prefetch_count=10)
    channel.basic_consume(queue='notifications.push.firebase', on_message_callback=callback)
    channel.start_consuming()

هذا النموذج يربط بشكل مباشر بين RabbitMQ وFirebase عبر Worker، ويبيّن بشكل عملي كيف يعمل نظام rabbitmq push notifications firebase.

خلاصة

استخدام RabbitMQ كوسيط رسائل مع Firebase Cloud Messaging يمنحك نظام Push Notifications:

  • قابل للتوسع (Scalable) مع عدد كبير من الإشعارات والمستخدمين.
  • منفصل المسؤوليات (Decoupled) بين منطق الأعمال ومنطق الإرسال.
  • أسهل في الإدارة، المراقبة، والتجربة.
  • قابل للتكامل مع قنوات أخرى مثل Email أو SMS بنفس النمط المعماري.

إذا كنت ترغب في تعميق الفهم حول تصميم أنظمة إشعارات قوية باستخدام RabbitMQ في بيئة Microservices، راجع أيضاً:

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

حول المحتوى:

دليل عملي لبناء نظام Push Notifications وربطه مع Firebase باستخدام RabbitMQ كوسيط.

هل كان هذا مفيدًا لك؟

أضف تعليقك