اتخطّى للمحتوى

المرحلة 04 · الـ APIs والتكاملدرس 25 من 3416 دقيقة قراءةآخر تحديث:

Lesson 25 / 34

RabbitMQعلى تدفّق موجود.

إزاي تاخد عملية شغّالة بالفعل وتحوّلها لمعالجة في الخلفية — القرار الأول (إيه يستاهل)، وبعدين نقطة الربط، والتطبيق الكامل على الـ checkout مع التعامل مع الفشل والتكرار.

● عملي مثال checkout كامل Idempotency التشغيل والمراقبة

عشان تنقل عملية شغّالة في Magento 2 لـ RabbitMQ: اختار العمليات اللي العميل مش مستني نتيجتها وبتعتمد على نظام خارجي، زي إرسال الأوردر لـ ERP. انشر من observer على sales_order_save_commit_after بعد تأكيد الحفظ، وابعت الـ id مش الكائن كامل، واعمل consumer بيشيّك جدول تتبّع بقيد unique قبل المعالجة، لأن الـ queue بيضمن التوصيل مرة على الأقل مش مرة بالظبط.

// الفرق عن درس الـ RabbitMQ السابق الدرس اللي فات كان "إزاي تبني موديول queue من الصفر" — الملفات الأربعة والـ publisher والـ consumer. الدرس ده عن "إزاي تقرّر إيه يروح queue وتربطه بكود شغّال". لو لسه ماقريتش الأول، الجزء التقني هيبقى أوضح بعده — بس ده يقرا مستقل.
◆ Part 1 — القرار
01

إيه اللي يستاهل يروح queue

أهم قرار — ولو غلط، الـ queue بيضيف تعقيد من غير فايدة.

اسأل نفسك الأربعة دول عن أي عملية
question
1 — هل المستخدم محتاج نتيجتها فوراً؟
لو العميل لازم يشوف النتيجة على الشاشة، العملية متزامنة بالضرورة.
للـ queueلو الإجابة "لأ" — زي إرسال الأوردر لـ ERP.
2 — هل بتعتمد على نظام خارجي؟
أي نداء لخدمة برّه سيرفرك = نقطة فشل مالكش سيطرة عليها.
للـ queueدايماً. الـ queue بيحمي تدفّقك من بطء أو سقوط الخدمة التانية.
3 — هل بتاخد وقت ملحوظ؟
أي حاجة فوق ~200 مللي ثانية بتبان في تجربة المستخدم.
للـ queueلو تقيلة ومش لازم تكون فورية.
4 — هل ممكن تتنفّذ مرتين بأمان؟
الـ queue بيضمن التوصيل، مش عدم التكرار.
تحذيرلو التكرار خطر (خصم فلوس مثلاً)، محتاج idempotency قبل ما تنقلها.
أمثلة تستاهل
  • إرسال أوردر لـ ERP — نظام خارجي، بطيء، العميل مش محتاج ينتظره.
  • إشعار push لتطبيق الموبايل — خدمة خارجية، والتأخير ثانيتين مقبول.
  • تحديث مخزون في نظام POS — تكامل، مش فوري بالضرورة.
  • توليد PDF للفاتورة — تقيل، ومش مطلوب لحظة الشراء.
  • مزامنة العميل مع CRM — خارجي وغير حرج.
أمثلة ماتستاهلش
  • حجز المخزون — لازم يحصل فوراً وإلا تبيع حاجة مش موجودة.
  • تفويض الدفع — العميل لازم يعرف نجح ولا لأ قبل ما يمشي.
  • حساب الإجماليات — النتيجة مطلوبة على الشاشة.
  • التحقق من الكوبون — العميل مستني الرد.
تشبيه الـ queue زي صندوق البريد الصادر في مكتب. الحاجات اللي ممكن تتبعت وتتعالج بعدين بتترمي فيه. لكن لو حد واقف مستني توقيعك دلوقتي، مينفعش تقوله "حطه في الصندوق وتعالى بكرة".
• • •
02

فين تحط نقطة الإطلاق

ثلاث اختيارات، وكل واحد له موقفه.

hook point
Observer على حدث موجود
تسمع لحدث Magento وتنشر الرسالة.
الأفضللما تضيف سلوك على كود مش بتاعك — زي الأوردرات. مفيش تعديل على الـ core.
نداء مباشر في كودك
تنشر من جوّه الـ service بتاعك.
الأفضللما تكون مالك الكود. أوضح وأسهل في التتبّع.
Plugin على method
تعترض method وتنشر بعدها.
نادراًبس لو مفيش حدث مناسب. الـ plugin أثقل ومربوط بتوقيع method ممكن يتغيّر.
!
أهم نقطة في القسم ده: انشر بعد ما الـ transaction تتأكّد، مش جوّاها. لو نشرت جوّه transaction وفشلت بعدها، بتبقى بعتّ رسالة عن أوردر مش موجود — والـ consumer هيحاول يحمّله ويفشل، أو الأسوأ، يبعت بيانات خاطئة للنظام الخارجي.
الأحداث الآمنة للنشر

استخدم الأحداث اللي بتتطلق بعد تأكيد الحفظ — اللي أسماؤها بتنتهي بـ _commit_after، أو checkout_submit_all_after اللي بيتطلق بعد اكتمال الـ checkout كله.

خطر

النشر في _save_after — لسه جوّه الـ transaction، وممكن تفشل بعدها.

آمن

النشر في _save_commit_after — الحفظ اتأكّد فعلاً.

i
لو مش متأكد من توقيت حدث معيّن، الأأمن إنك تختبره: اعمل observer يسجّل، وشغّل سيناريو بيفشل عمداً في النص، وشوف الرسالة اتبعتت ولا لأ.
• • •
03

تصميم الرسالة

قرار صغير بيفرق كتير في الصيانة.

متبعتش الكائن كامل

الأوردر كله في الرسالة — حجم كبير، وبيانات قديمة لما الـ consumer يشتغل.

ابعت المعرّف

الـ id بس، والـ consumer يحمّل النسخة الحالية من قاعدة البيانات.

ليه المعرّف أفضل
  • البيانات دايماً حديثة — الـ consumer ممكن يشتغل بعد دقايق، والأوردر ممكن يكون اتغيّر.
  • الرسالة خفيفة — أسرع في النقل وأقل ذاكرة.
  • مفيش مشاكل serialization — الكائنات المعقّدة بتسبب مشاكل في التحويل.
  • التغيير أسهل — لو ضفت حقل للأوردر، مش محتاج تغيّر شكل الرسالة.
تجنّب — الكائن كاملphp
$this->publisher->publish(
    'vendor.order.sync',
    json_encode($order->getData())  // ثقيل وقديم
);
الأفضل — المعرّف والسياقphp
$message = json_encode([
    'order_id'  => (int) $order->getId(),
    'increment' => $order->getIncrementId(),  // للتتبّع
    'store_id'  => (int) $order->getStoreId(),
    'event'     => 'order_placed',                 // للتمييز
    'queued_at' => gmdate('c'),                   // للتشخيص
]);

$this->publisher->publish('vendor.order.sync', $message);
i
الـ queued_at وincrement مش ضروريين للمنطق، بس بيوفّروا وقت كبير في التشخيص — بتعرف الرسالة قعدت قد إيه في الطابور وتربطها بأوردر بسهولة.
• • •
◆ Part 2 — المثال: الـ Checkout
04

تحليل الـ checkout

نطبّق الأربع أسئلة على عمليات الـ checkout الحقيقية.

operation
تفويض الدفع
التأكد إن الكارت صالح والمبلغ متاح.
يفضل متزامنالعميل لازم يعرف نجح ولا فشل قبل ما يسيب الصفحة.
حجز المخزون
خصم الكمية المتاحة.
يفضل متزامنأي تأخير = احتمال بيع نفس القطعة مرتين.
إنشاء الأوردر
تحويل الـ quote لـ order.
يفضل متزامنده الناتج اللي العميل بيستناه.
إرسال الأوردر لـ ERP
مزامنة مع نظام إدارة الموارد.
ينقل للـ queueنظام خارجي، بطيء، والعميل مش محتاجه.
إيميل التأكيد
إشعار العميل بالطلب.
ينقل للـ queueخدمة خارجية. تأخير ثواني مقبول تماماً.
إشعار push للتطبيق
تنبيه في تطبيق الموبايل.
ينقل للـ queueخارجي وغير حرج.
تحديث نقاط الولاء
إضافة نقاط للعميل.
ينقل للـ queueمش مطلوب لحظياً، والعميل هيشوفها في حسابه.
إرسال بيانات للتحليلات
تتبّع التحويل في أدوات القياس.
ينقل للـ queueخارجي بالكامل ومالوش أثر على العميل.
الشكل بعد التحويل
دفع
مخزون
أوردر
نشر رسالة
شكراً

العميل بيشوف صفحة الشكر بعد إنشاء الأوردر مباشرة. الـ ERP والإيميل والإشعار بيحصلوا في الخلفية بعد كده.

i
هنبني المثال على مزامنة ERP لأنها أوضح حالة، وأكتر واحدة بتظهر في مشاريع فيها تطبيق موبايل أو نظام إدارة خارجي.
• • •
05

جدول التتبّع

قبل الكود — ده اللي هيخلّي الـ idempotency ممكنة.

ليه جدول أصلاً

الـ queue ممكن يوصّل نفس الرسالة أكتر من مرة (لو الـ consumer وقع قبل التأكيد). محتاج تعرف "أنا عالجت الأوردر ده قبل كده ولا لأ" — والجدول ده هو الذاكرة دي.

etc/db_schema.xmlxml
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <table name="vendor_order_sync" resource="default">
    <column xsi:type="int" name="entity_id"
      unsigned="true" identity="true"/>
    <column xsi:type="int" name="order_id" unsigned="true"/>
    <column xsi:type="varchar" name="status" length="20"/>
    <column xsi:type="int" name="attempts" default="0"/>
    <column xsi:type="text" name="last_error" nullable="true"/>
    <column xsi:type="timestamp" name="updated_at"
      default="CURRENT_TIMESTAMP" on_update="true"/>

    <constraint xsi:type="primary" referenceId="PRIMARY">
      <column name="entity_id"/>
    </constraint>

    <!-- المفتاح: أوردر واحد = صف واحد -->
    <constraint xsi:type="unique" referenceId="VENDOR_ORDER_SYNC_ORDER_ID">
      <column name="order_id"/>
    </constraint>
  </table>
</schema>
!
القيد الفريد على order_id هو قلب الحماية. حتى لو حصل سباق وشغّلت رسالتين لنفس الأوردر في نفس اللحظة، قاعدة البيانات هترفض التاني. الحماية على مستوى قاعدة البيانات أقوى من أي شيك في الكود.
• • •
06

ملفات الإعداد

الأربعة المعتادين — مختصرين هنا.

etc/communication.xmlxml
<config>
  <topic name="vendor.order.sync"
    request="string"
    handler="Vendor\OrderSync\Model\Consumer::process"/>
</config>
etc/queue_topology.xmlxml
<config>
  <exchange name="magento" type="topic" connection="amqp">
    <binding id="orderSyncBinding"
      topic="vendor.order.sync"
      destinationType="queue"
      destination="vendor.order.sync"/>
  </exchange>
</config>
etc/queue_publisher.xmlxml
<config>
  <publisher topic="vendor.order.sync">
    <connection name="amqp" exchange="magento"/>
  </publisher>
</config>
etc/queue_consumer.xmlxml
<config>
  <consumer name="vendorOrderSync"
    queue="vendor.order.sync"
    connection="amqp"
    handler="Vendor\OrderSync\Model\Consumer::process"/>
</config>
i
الـ vendorOrderSync هو الاسم اللي هتستخدمه في أوامر التشغيل والمراقبة — خليه واضح ومميّز.
• • •
07

نقطة الربط — الـ Observer

هنا بنربط بالتدفّق الموجود من غير ما نلمسه.

etc/events.xmlxml
<config>
  <!-- بعد تأكيد الحفظ، مش قبله -->
  <event name="sales_order_save_commit_after">
    <observer name="vendor_queue_order_sync"
      instance="Vendor\OrderSync\Observer\QueueOrderSync"/>
  </event>
</config>
Observer/QueueOrderSync.phpphp
namespace Vendor\OrderSync\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\MessageQueue\PublisherInterface;
use Psr\Log\LoggerInterface;

class QueueOrderSync implements ObserverInterface
{
    private const TOPIC = 'vendor.order.sync';

    public function __construct(
        private readonly PublisherInterface $publisher,
        private readonly LoggerInterface $logger
    ) {}

    public function execute(Observer $observer): void
    {
        $order = $observer->getEvent()->getOrder();

        if (!$order || !$order->getId()) {
            return;
        }

        // الحدث بيتطلق مع أي حفظ — ننشر عند الإنشاء بس
        if (!$order->isObjectNew()) {
            return;
        }

        try {
            $this->publisher->publish(self::TOPIC, json_encode([
                'order_id'  => (int) $order->getId(),
                'increment' => $order->getIncrementId(),
                'store_id'  => (int) $order->getStoreId(),
                'queued_at' => gmdate('c'),
            ]));
        } catch (\Exception $e) {
            // الأوردر اتعمل بنجاح — منوقّعوش بسبب الطابور
            $this->logger->critical(
                'Failed to queue order sync: ' . $e->getMessage(),
                ['order_id' => $order->getId()]
            );
        }
    }
}
تلات قرارات في الكود ده
  • _commit_after مش _save_after — ضمان إن الأوردر موجود فعلاً.
  • شيك isObjectNew() — الحدث بيتطلق مع كل حفظ للأوردر، مش عند الإنشاء بس. من غيره هتبعت رسالة كل مرة الأوردر يتغيّر.
  • try/catch حوالين النشر — لو RabbitMQ واقع، الأوردر المفروض يكمّل عادي. الفشل بيتسجّل كـ critical عشان يتراجع.
!
لو ضاعت رسالة بسبب سقوط RabbitMQ، الأوردر مش هيتزامن أبداً. عشان كده الـ log بـ critical، والأفضل تعمل مهمة cron احتياطية بتدوّر على الأوردرات اللي مالهاش صف في جدول التتبّع وتعيد نشرها.
• • •
08

الـ Consumer مع idempotency

الجزء اللي بيحدد جودة التكامل كله.

Model/Consumer.phpphp
namespace Vendor\OrderSync\Model;

class Consumer
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly SyncLog $syncLog,
        private readonly ErpClient $erp,
        private readonly LoggerInterface $logger
    ) {}

    public function process(string $message): void
    {
        $data = json_decode($message, true);
        $orderId = (int) ($data['order_id'] ?? 0);

        if (!$orderId) {
            // رسالة تالفة — سجّل واخرج، متحاولش تاني
            $this->logger->error('Malformed message', ['raw' => $message]);
            return;
        }

        // 1. الحماية من التكرار
        if ($this->syncLog->isDone($orderId)) {
            return;  // اتعالج قبل كده
        }

        try {
            // 2. حمّل النسخة الحالية
            $order = $this->orderRepository->get($orderId);

            // 3. العملية الخارجية
            $this->erp->sendOrder($order);

            // 4. سجّل النجاح
            $this->syncLog->markDone($orderId);

        } catch (NoSuchEntityException $e) {
            // الأوردر مش موجود — إعادة المحاولة مش هتنفع
            $this->syncLog->markFailed($orderId, 'order not found');

        } catch (\Exception $e) {
            // فشل مؤقت — سجّله عشان الـ cron يعيد
            $this->syncLog->markRetry($orderId, $e->getMessage());
            $this->logger->error('ERP sync failed', [
                'order_id' => $orderId,
                'error'    => $e->getMessage(),
            ]);
        }
    }
}
التمييز بين نوعي الفشل

ده أهم تفصيل في الكود. فشل دائم (أوردر مش موجود، رسالة تالفة) — إعادة المحاولة هتفشل بنفس الطريقة للأبد، فنسجّله ونخرج. فشل مؤقت (الـ ERP واقع، انقطاع شبكة) — يستاهل إعادة.

!
لو ماميّزتش بينهم، رسالة تالفة واحدة هتفضل تتعاد إلى الأبد وتملا الـ log وتستهلك موارد — ودي حالة معروفة اسمها "رسالة سامة" (poison message).
ليه الشيك على isDone في الأول

لأن الـ queue بيضمن التوصيل مرة على الأقل، مش مرة بالظبط. لو الـ consumer عالج الأوردر وبعت للـ ERP ووقع قبل ما يأكّد استلام الرسالة، الرسالة هترجع — والشيك ده هو اللي بيمنع إرسالها للـ ERP مرتين.

• • •
◆ Part 3 — التشغيل
09

الفشل وإعادة المحاولة

الجزء اللي بيتنسي لحد ما يحصل مشكلة.

ليه cron مش الـ queue نفسه

إعادة المحاولة المدمجة في الـ queue محدودة وصعب التحكم فيها. الأسهل والأوضح: سجّل الفشل في جدولك، واعمل cron بيدوّر على اللي فشل ويعيد نشره بتباعد متزايد.

Cron/RetryFailed.php (مبسّط)php
public function execute(): void
{
    // اللي فشل ولسه محاولاته أقل من الحد
    $rows = $this->syncLog->getRetryable(maxAttempts: 5);

    foreach ($rows as $row) {
        // تباعد متزايد: 2^n دقيقة
        $waitMinutes = 2 ** (int) $row['attempts'];
        if (!$this->waitedEnough($row, $waitMinutes)) {
            continue;
        }

        $this->publisher->publish('vendor.order.sync', json_encode([
            'order_id' => (int) $row['order_id'],
            'retry'    => true,
        ]));
    }
}
ليه التباعد المتزايد

لو الـ ERP واقع، إعادة المحاولة كل دقيقة بتزوّد الضغط عليه وبتأخّر تعافيه. التباعد المتزايد (دقيقتين، أربعة، تمانية...) بيدّي الخدمة فرصة ترجع.

بعد استنفاد المحاولات

بعد الحد الأقصى، علّم الصف كـ failed نهائياً ونبّه حد — إيميل أو Slack. أوردر مش متزامن مع الـ ERP مشكلة تشغيلية محتاجة تدخّل بشري، مش حاجة تتسجّل في log ومحدش يقراها.

i
اعمل شاشة بسيطة في الأدمن بتعرض الأوردرات الفاشلة مع سبب الفشل وزرار "أعد المحاولة". بتوفّر وقت كبير على فريق التشغيل وبتمنعهم من إنهم يجولك في كل مرة.
• • •
10

تشغيل الـ Consumers

الـ consumer مايشتغلش لوحده — لازم حاجة تشغّله وتحافظ عليه.

التشغيل اليدوي — للتطويرbash
# شغّال باستمرار
bin/magento queue:consumers:start vendorOrderSync

# عدد محدود — مفيد للاختبار
bin/magento queue:consumers:start vendorOrderSync \
  --max-messages=10

# شوف كل الـ consumers المتاحة
bin/magento queue:consumers:list
method
يدوي
تشغيل مباشر من الطرفية.
امتىالتطوير والاختبار بس. بيقف لما تقفل الجلسة.
عن طريق cron
Magento بيشغّل الـ consumers دورياً بإعداد في env.php.
امتىالأبسط للإنتاج. مناسب لو التأخير بدقائق مقبول.
Supervisor
أداة بتشغّل الـ consumer باستمرار وترجّعه لو وقع.
امتىلما تحتاج معالجة شبه فورية أو حجم كبير.
app/etc/env.php — تشغيل بالـ cronphp
'cron_consumers_runner' => [
    'cron_run'     => true,
    'max_messages' => 1000,
    'consumers'    => ['vendorOrderSync'],
],
!
الـ max_messages مهم. الـ consumer عملية PHP طويلة العمر، والذاكرة بتتراكم مع الوقت. الحد ده بيخلّيه يقفل ويرجع بعد عدد معيّن — إعادة تدوير مقصودة تمنع تسريب الذاكرة.
i
لو محتاجت معالجة أسرع، شغّل أكتر من نسخة من نفس الـ consumer. RabbitMQ بيوزّع الرسائل عليهم تلقائياً. بس اتأكد إن الـ idempotency سليمة الأول — لأن دلوقتي بقى فيه احتمال حقيقي لمعالجة متوازية.
• • •
11

المراقبة والتشخيص

الطوابير بتفشل بصمت — ده أخطر ما فيها.

المؤشرات اللي تهمك
  • طول الطابور — لو بيزيد باستمرار، الـ consumer واقف أو بطيء.
  • عمر أقدم رسالة — بيقولك التأخير الفعلي.
  • عدد الـ consumers الشغّالة — صفر = مفيش معالجة خالص.
  • معدّل الفشل — ارتفاع مفاجئ يعني النظام الخارجي عنده مشكلة.
أوامر التشخيصbash
# طول الطوابير (من سيرفر RabbitMQ)
rabbitmqctl list_queues name messages consumers

# هل الـ consumer شغّال؟
ps aux | grep queue:consumers:start

# أخطاء المزامنة في الـ log
grep "ERP sync failed" var/log/*.log | tail -20
فحص جدول التتبّعsql
-- توزيع الحالات
SELECT status, COUNT(*) FROM vendor_order_sync
GROUP BY status;

-- الأوردرات اللي فشلت نهائياً
SELECT order_id, attempts, last_error, updated_at
FROM vendor_order_sync
WHERE status = 'failed'
ORDER BY updated_at DESC;

-- أوردرات مالهاش صف أصلاً (رسالة ضاعت)
SELECT o.entity_id FROM sales_order o
LEFT JOIN vendor_order_sync s ON s.order_id = o.entity_id
WHERE s.order_id IS NULL
  AND o.created_at < NOW() - INTERVAL 1 HOUR;
!
الاستعلام التالت هو الأهم. بيكشف الأوردرات اللي الرسالة بتاعتها ضاعت تماماً — دي الحالة اللي مفيش أي log بيمسكها، وبتتكتشف عادة لما عميل يتصل يسأل عن طلبه.
الاختبار المحلي
دورة اختبار كاملةbash
# 1. اعمل أوردر من الواجهة أو برمجياً

# 2. شوف الرسالة وصلت الطابور
rabbitmqctl list_queues name messages

# 3. شغّل رسالة واحدة وراقب
bin/magento queue:consumers:start vendorOrderSync \
  --max-messages=1

# 4. اتأكد من التسجيل
#    SELECT * FROM vendor_order_sync ORDER BY entity_id DESC LIMIT 1;

# 5. اختبر الحماية من التكرار: أعد نشر نفس الرسالة
#    المفروض الـ consumer يتجاهلها
i
الخطوة الخامسة هي اللي معظم الناس بتتخطاها. اختبار الـ idempotency صراحةً أهم من اختبار المسار الناجح، لأن التكرار بيحصل في الإنتاج بس — ووقتها بيبقى متأخر.
• • •
12

الفخاخ الشائعة

اقرا دي حتى لو مش هتقرا حاجة تانية.

  • النشر جوّه transaction — لو فشلت، بعتّ رسالة عن حاجة مش موجودة. انشر في _commit_after.
  • مفيش idempotency — التكرار وارد دايماً. بدونها هتبعت نفس الأوردر للـ ERP مرتين.
  • إرسال الكائن كامل — بيانات قديمة ورسالة تقيلة. ابعت المعرّف.
  • عدم التمييز بين الفشل المؤقت والدائم — رسالة تالفة بتتعاد للأبد.
  • نسيان isObjectNew() — رسالة بتتبعت مع كل حفظ للأوردر مش عند الإنشاء بس.
  • مفيش max_messages — الـ consumer بيستهلك ذاكرة لحد ما يقع.
  • مفيش مراقبة — الطابور بيتراكم لأسبوع ومحدش واخد باله.
  • استثناء غير محسوب في الـ publisher — سقوط RabbitMQ بيوقّع الـ checkout. لفّه في try/catch.
  • نقل عملية متزامنة بطبيعتها — الدفع والمخزون لازم يفضلوا فوريين.
i
لو هتفتكر حاجة واحدة: الـ queue بيضمن التوصيل مرة على الأقل، مش مرة بالظبط. كل تصميم سليم بيبدأ من الحقيقة دي — وكل مشكلة تكرار في الإنتاج سببها إنها اتنسيت.

RabbitMQ على تدفّق موجود ✓

دلوقتي عندك الطريقة كاملة: تقرّر إيه يستاهل، تختار نقطة الربط الصح، تصمّم الرسالة، تكتب consumer آمن ضد التكرار، وتشغّله وتراقبه في الإنتاج.

الخلاصة: انشر بعد تأكيد الحفظ، ابعت المعرّف مش الكائن، واعتبر التكرار حاصل لا محالة. التلاتة دول بيفرّقوا بين تكامل بيشتغل وتكامل بيسبّب مشاكل صامتة.

// magento 2 · rabbitmq · existing flow · checkout

خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.

كاتب الدرس

Abdulrahman Masoud

عندك سؤال على الدرس أو محتاج مساعدة في مشروع Magento؟ كلّمني.

أسئلة شائعة

إيه العمليات اللي تستاهل تروح message queue في Magento 2؟

اسأل أربع أسئلة: العميل محتاج النتيجة فورًا؟ بتعتمد على نظام خارجي؟ بتاخد وقت ملحوظ؟ ممكن تتنفّذ مرتين بأمان؟ إرسال الأوردر لـ ERP وإيميل التأكيد والـ push notification ونقاط الولاء والتحليلات تستاهل. تفويض الدفع وحجز المخزون وإنشاء الأوردر والتحقق من الكوبون لازم يفضلوا متزامنين لأن العميل مستني نتيجتهم على الشاشة.

أنشر الرسالة في أنهي event في Magento 2؟

بعد ما الـ transaction تتأكّد، يعني sales_order_save_commit_after أو checkout_submit_all_after، مش _save_after اللي لسه جوّه الـ transaction وممكن تفشل بعده فتبعت رسالة عن أوردر مش موجود. في الـ observer اتأكد بـ isObjectNew إنك بتنشر عند الإنشاء بس، ولفّ النشر في try/catch عشان لو RabbitMQ واقع الأوردر يكمّل عادي والفشل يتسجّل كـ critical.

أبعت الأوردر كامل في الرسالة ولا الـ id بس؟

الـ id بس مع سياق بسيط زي increment_id و store_id و queued_at، والـ consumer يحمّل النسخة الحالية من قاعدة البيانات. الكائن كامل بيبقى تقيل، وبياناته بتبقى قديمة لأن الـ consumer ممكن يشتغل بعد دقايق والأوردر اتغيّر، وبيسبب مشاكل serialization، وأي حقل جديد بيغيّر شكل الرسالة. الـ queued_at والـ increment بيوفّروا وقت كبير في التشخيص.

إزاي أعمل idempotency في consumer بتاع Magento 2؟

جدول تتبّع فيه order_id بقيد unique و status و attempts و last_error. الـ consumer بيشيّك isDone الأول ويخرج لو اتعالج، وبعدين يحمّل الأوردر وينفّذ العملية الخارجية ويعلّم النجاح. القيد الفريد في قاعدة البيانات بيحمي حتى لو رسالتين اتعالجوا في نفس اللحظة. الـ queue بيضمن التوصيل مرة على الأقل مش مرة بالظبط، فالتكرار حاصل لا محالة.

إزاي أفرّق بين الفشل المؤقت والدائم في الـ consumer؟

الفشل الدائم زي أوردر مش موجود أو رسالة تالفة إعادة المحاولة هتفشل فيه بنفس الطريقة للأبد، فسجّله كـ failed واخرج وإلا هتبقى عندك poison message بتملا الـ log. الفشل المؤقت زي الـ ERP واقع أو انقطاع شبكة يستاهل إعادة: علّمه retry في جدول التتبّع، وخلّي cron يعيد نشره بتباعد متزايد لحد أقصى من المحاولات، وبعدها نبّه حد.

إزاي أشغّل الـ consumers في production في Magento 2؟

أبسط طريقة cron_consumers_runner في env.php بـ cron_run true و max_messages وقائمة الـ consumers، والـ cron بيشغّلهم دوريًا ومناسب لو التأخير بدقايق مقبول. لو محتاج معالجة شبه فورية استخدم Supervisor بيشغّل queue:consumers:start باستمرار ويرجّعه لو وقع. الـ max_messages مهم لأنه بيعيد تدوير عملية PHP طويلة العمر قبل ما الذاكرة تتراكم.

إزاي أعرف إن رسالة ضاعت من الطابور؟

راقب طول الطابور وعمر أقدم رسالة وعدد الـ consumers الشغّالة بـ rabbitmqctl list_queues name messages consumers. والأهم استعلام LEFT JOIN بين sales_order وجدول التتبّع بيطلّع الأوردرات اللي مالهاش صف أصلًا بعد ساعة من إنشائها، لأن دي الحالة اللي مفيش log بيمسكها. اعمل cron احتياطي يعيد نشر الأوردرات دي، واختبر الـ idempotency صراحةً بإعادة نشر نفس الرسالة.