الإجابة المختصرة

تُشكل الإجابة على سؤال "Flow أم Apex" غالبًا غير صحيحة عندما يتم تقييمها بناءً على سهولة الكتابة أو توفر المطورين. تعتمد الإجابة الصحيحة على أربعة عوامل فنية: عدد السجلات التي تمر عبر معاملة واحدة، وما إذا كانت المنطقية تتطلب Atomicity كاملة، ومدى تعقيد الشروط والفروع، ومن سيتولى صيانة المكون بعد عام. يعتبر Flow هو الخيار الافتراضي الصحيح لمعظم أتمتة الأعمال، ولكن هناك نقاط تحول واضحة حيث يؤدي الاستمرار في العمل باستخدام Flow إلى مخاطر تشغيلية، وليس مجرد "رمز أقل أناقة."

تتناول هذه المقالة قرار الاختيار نفسه: كيفية تحديد مسبقًا أن المنطقية تستدعي Apex، وكيفية منع اتخاذ القرار كخيار افتراضي بدلاً من تقييم مدروس. تُناقش مسألة تنظيف Flow-ات وعمليات الأتمتة الحالية التي تراكمت كديون تقنية في مقال منفصل ولا تشكل جزءًا من هذا النقاش.

ما الذي يميزهما فعليًا على مستوى المنصة؟

Flow هو محرك تصريحي (Declarative) يُترجم في وقت التشغيل إلى تعليمات تنفذ DML و SOQL نيابة عن المستخدم، بينما Apex هو رمز مُبرمج (Compiled code) يعمل ضمن نفس حدود الحاكم (Governor Limits) ولكن مع تحكم مباشر في تسلسل العمليات. الفرق العملي الأول هو Bulkification: يقوم مطور Apex بإنشاء حلقة بشكل صريح تجمع جميع السجلات في مصفوفة واحدة وتنفذ DML واحدًا، بينما في Flow، من السهل إنشاء حلقة (Loop) تنفذ عملية DML أو استعلام في كل تكرار بشكل منفصل - وهو نمط يصل إلى حد 101 استعلام المسموح به بشكل أسرع بكثير.

الفرق الثاني هو التحكم في المعاملات (Transaction). يتيح Apex استخدام Savepoint و Database.rollback للتراجع الجزئي، ومعالجة DmlException على مستوى سجل فردي عبر Database.insert(list, false)، ومنطقًا شرطيًا معقدًا "دون قيود على عمق الأفرع". في Flow، يتم تعريف معالجة الأخطاء على مستوى Fault Path لكل عنصر، ويعمل هذا جيدًا للسيناريوهات الخطية ولكنه يصبح صعب التتبع عندما يكون هناك أكثر من مسارات فشل متوازية.

إطار عمل القرار: أربعة اختبارات قبل اختيار الأداة

اختبار الحجم

قاعدة عامة: إذا كانت العملية تعمل على سجل واحد نتيجة لإجراء المستخدم (إنشاء عميل محتمل، تغيير حالة فرصة)، فإن Flow يكفي دائمًا تقريبًا. أما إذا كانت العملية تعمل على عشرات إلى آلاف السجلات في وقت واحد – تحديث دوري، معالجة دفعة Batch تأتي من تكامل، تنظيف بيانات مجدول – فإن Apex مع Batchable أو Queueable هو الخيار الآمن، لأنه يمنح تحكمًا كاملاً في الـ Bulkification وإدارة Governor Limits في مواجهة الحجم المتغير.

اختبار الـ Atomicity

يجب أن نسأل: إذا فشل جزء من التحديث، هل يجوز أن يبقى الجزء الآخر محفوظًا؟ إذا كانت الإجابة "لا" – على سبيل المثال تحديث أمر وإنشاء سجل دفع يجب أن يحدثا معًا – فإن Apex مع Savepoint هو الطريقة الصحيحة لضمان ذلك. لا يوفر Flow دمجًا (Rollback) كاملاً بين العناصر دون بناء يدوي ومعقد لمنطق التعويض.

اختبار تعقيد الفروع

يصبح Flow الذي يحتوي على أكثر من 6-8 عناصر قرار متداخلة صعب القراءة ومكلفًا للاختبار، حتى لو كان كل فرع بسيطًا. عندما يتجاوز تعقيد المنطق التجاري هذا الحد، فإن كتابة نفس المنطق كدالة Apex موثقة باختبارات الوحدة (@isTest) تكون عادةً أرخص في الصيانة، حتى لو كان وقت الكتابة الأولي أطول.

اختبار الصيانة والملكية

يجب أن نسأل من سيصون المكون بعد عام، وليس من يبنيه الآن. إذا كان فريق Admin هو الذي سيحتاج إلى تحديث قواعد العمل بشكل دوري - مثل تغيير شروط الخصم أو القيم الحدية - فإن Flow أفضل حتى لو كان Apex "أنظف" تقنيًا، لأنه متاح للتحديث دون دورة نشر. أما إذا كانت التغييرات تتطلب معرفة بمخطط البيانات واختبارات الانحدار (Regression Testing)، فإن Apex هو الخيار الصحيح حتى لو كان هناك فريق تطوير صغير فقط يتولى صيانته.

جدول القرار

المعياراختر Flowاختر Apex
حجم السجلات في معاملة واحدةحتى بضع عشراتالمئات إلى الآلاف
متطلبات الذرية (Atomicity) بين عدة كائناتغير حاسمةحاسمة - يتطلب تراجعًا كاملاً
عدد فروع القرارحتى 6-8 تقريباًأكثر من ذلك، أو منطق متكرر
وتيرة تغيير قواعد العملمتكررة، من قبل المسؤولنادرة، تتطلب اختبارات انحدار
الحاجة إلى استدعاء API خارجي معقداستدعاء بسيط واحد (HTTP Callout)منطق Retry، مصادقة معقدة أو دفعة Batch
متطلبات الاختبارات الآلية (CI)محدودةكاملة، @isTest مع التغطية
التكامل مع وظيفة مجدولة ثابتة (Scheduled Job)غير مناسب مباشرةطبيعية عبر Schedulable

سيناريو مثال: شركة معدات طبية بعملية موافقة على الطلبات

قامت شركة معدات طبية متوسطة الحجم تضم حوالي 40 مندوب مبيعات بتفعيل Flow واحد لعملية الموافقة على الطلبات: التحقق من المخزون، حساب الخصم، إنشاء سجل موافقة، وإرسال تنبيه للمدير. في البداية، عمل هذا بشكل جيد على طلب واحد. بعد ستة أشهر، أُضيف سيناريو جديد – استيراد طلبات دفعة من ملف تكامل مع نظام ERP، مما ينشئ ما بين 200 و 800 طلب بشكل متزامن.

قام الـ Flow، الذي تم تشغيله عبر Record-Triggered Flow على مستوى "لكل سجل"، بتنفيذ استعلام للتحقق من المخزون ضمن كل عملية تشغيل منفصلة. مع استيراد 500 طلب، تجاوز النظام حد 100 استعلام في معاملة واحدة وفشلت الطلبات دون رسالة خطأ واضحة للمستخدم. أدرك الفريق أن المشكلة ليست في الـ Flow نفسه، بل في التوافق بين عملية صُممت لسجل واحد وبين سيناريو حجم لم يكن موجودًا وقت البناء.

لم يكن الحل هو التخلص من الـ Flow. قام الفريق بتقسيم المنطق: بقي الـ Flow مسؤولاً عن العملية اليدوية للطلب الواحد (اختبار الحجم منخفض، وهناك حاجة لتحديث متكرر لقواعد الخصم من قبل المسؤول)، بينما تم نقل عملية الاستيراد بالجملة إلى Apex Batch Job تقوم بتنفيذ Bulkification كامل، وتتحقق من المخزون في استعلام مركزي واحد، وتنفذ DML واحدًا لجميع السجلات. يستدعي كلا الآليتين نفس طبقة المنطق التجاري المشترك (Apex Class واحدة يستدعيها الـ Flow أيضًا عبر Invocable Method)، حتى لا يتم صيانة قاعدة الخصم مرتين.

المخاطر الشائعة والإجراءات الوقائية

المخاطركيف تبدو في الواقعالإجراء الوقائي
Flow على حجم يتزايد تدريجياًالعملية عملت لمدة ستة أشهر ثم فشلت بصمت بسبب حدود الحاكم (Governor Limits)التحقق من الحجم المتوقع مسبقًا وتخطيط نقطة تحول إلى Apex قبل الوصول إلى الحد الأقصى
ازدواجية منطق العمل في Flow و Apexمكانان يحسبان الخصم بشكل مختلفتركيز الحساب التجاري في طبقة Apex مشتركة يمكن لـ Flow استدعائها أيضاً
ترتيب التشغيل (Trigger Order) غير متوقعFlow-ات متعددة و Trigger على نفس الكائن تتعارضTrigger Handler مركزي واحد في Apex لكل كائن حرج
معالجة جزئية للأخطاء في Flow معقديتم تحديث بعض السجلات وليس كلها، دون رؤية واضحةنقل العمليات التي تتطلب Atomicity إلى Apex مع Savepoint
Apex بدون اختبارات كافيةتغيير بسيط يكسر عملية حرجة في النشر التاليطلب تغطية حقيقية وليس مجرد نسبة مئوية شكلية، بما في ذلك سيناريوهات الفشل

قائمة التحقق للقرار قبل البناء

  • تم التحقق من الحجم المتوقع لمدة عام، وليس الوضع الحالي فقط.
  • تم تحديد ما إذا كانت العملية تتطلب Atomicity بين عدة كائنات.
  • تم حساب فروع القرار المتوقعة في المنطق.
  • من المعروف من سيصون المكون وكم مرة ستتغير القواعد.
  • تم التحقق مما إذا كان هناك منطق مشابه موجودًا بالفعل في Apex أو في Flow آخر على نفس الكائن.
  • تم تحديد Trigger Order إذا كانت هناك آليات أتمتة متعددة على الكائن.
  • إذا تم اختيار Apex - تم تحديد سيناريوهات الاختبار بما في ذلك الفشل الجزئي.
  • إذا تم اختيار Flow - تم تحديد Fault Path لكل عنصر حرج.

كيف يرتبط هذا بالهندسة المعمارية الشاملة؟

يُعد اختيار الأداة المناسبة لأتمتة فردية مجرد طبقة واحدة ضمن صورة أوسع من هندسة حلول إدارة علاقات العملاء (CRM)، حيث يؤثر كل من نموذج البيانات والأذونات على ما يمكن لـ Flow أو Apex الوصول إليه. عندما تتجاوز الأتمتة حدود المؤسسة الخارجية – على سبيل المثال، التحقق من المخزون مقابل نظام ERP في الوقت الفعلي – يندمج الاختيار بين Flow و Apex أيضًا في اعتبارات أنماط التكامل وفي مسألة كيفية ربط Salesforce بنظام ERP من حيث زمن الاستجابة ومعالجة الفشل.

في المؤسسات التي تدير عددًا من المؤسسات (Orgs)، يجب أيضًا التحقق مما إذا كان منطق العمل متطابقًا في جميعها – وهي مسألة نوقشت في دليل Single Org مقابل Multi Org وتؤثر على ما إذا كان من المجدي تركيز المنطق في حزمة Apex مشتركة.

الخلاصة

الاختيار بين Flow و Apex ليس مسألة مهارة فريق أو تفضيل شخصي، بل هو نتيجة لأربعة اختبارات فنية: الحجم، Atomicity، تعقيد الفروع، وتكرار التغيير. يُعد Flow هو الخيار الافتراضي الصحيح لمعظم عمليات الأتمتة التي تتعلق بسجل واحد وتتغير بشكل متكرر. يصبح Apex ضروريًا عندما يكون هناك حجم كبير، عندما تكون هناك حاجة للتحكم الكامل في المعاملات، أو عندما يتجاوز التعقيد المنطقي الحد الذي لا يزال من الممكن صيانته من خلال واجهة تصريحية (Declarative). إن المؤسسة التي تضع هذه الاختبارات كجزء من عملية العمل - ولا تتركها لتقدير فردي من قبل كل مطور - توفر غالبية الحالات التي تنكسر فيها الأتمتة التي عملت بشكل جيد في البداية بصمت عندما يزداد الحجم.