الأدلة التقنية

أدوات أتمتة سير العمل: n8n وNode-RED وبدائلهما

كيف تختار أداة أتمتة سير العمل بعد أن تعرف ترخيصها الحقيقي وطريقة تسعيرها وما تفعله عندما يفشل طلب.

أدوات أتمتة سير العمل: n8n وNode-RED وبدائلهما

في المقال السابق ذكرنا أن هناك خمس طرق لربط الأنظمة، وأن أغلب الربط الداخلي بين تطبيقات الشركة يناسبه نوع من الأدوات يسمى أدوات الأتمتة المرئية، أو iPaaS، والسؤال التالي الطبيعي هو: أي أداة؟ وهنا تكتشف أن عدد الأدوات كبير، وأن كثيراً منها يوصف بأنه «مفتوح المصدر» وهو ليس كذلك بالمعنى المعتمد، وأن الأدوات السحابية تحسب السعر بطرق مختلفة تجعل المقارنة بينها صعبة.

المواضيع في السلسلة:

وسوف نناقش في هذا المقال ما يلي:

  • عائلات أدوات الأتمتة، وأين تقع كل أداة.
  • n8n وترخيصه Sustainable Use License: ماذا يسمح لك أن تفعل على سيرفرك، وما الذي يحتاج ترخيصاً مدفوعاً.
  • Node-RED وActivepieces وWindmill وHuginn، ثم Kestra وTemporal وAirflow، ثم محركات BPMN مثل Camunda وFlowable.
  • الخدمات السحابية Zapier وMake وPower Automate، وكيف يحسب كل منها السعر.
  • مختبر: التدفق نفسه (Webhook ثم تحويل ثم إشعار) مبنياً في ثلاث أدوات، مع قياس الذاكرة والسرعة وما يحدث عند الفشل.
  • جدول قرار يساعدك على الاختيار.
📌
التراخيص والأسعار في هذا المقال منقولة من نصوص التراخيص في مستودعات المشاريع ومن صفحات الأسعار الرسمية كما ظهرت يوم 14 أكتوبر 2026، وهي تتغير، فراجع المصدر المرتبط بكل معلومة قبل أن تبني عليها قراراً. وهذا شرح تقني وليس استشارة قانونية.

خمس عائلات من الأدوات

الأدوات التي تسمعها في هذا المجال لا تحل المشكلة نفسها، والصورة التالية تجمعها في خمس عائلات مع ترخيص كل أداة:

خمس مجموعات: أدوات الأتمتة المرئية وفيها n8n بترخيص Sustainable Use وActivepieces بترخيص MIT مع أجزاء EE وHuginn بترخيص MIT ثم الخدمات السحابية Zapier وMake وPower Automate، والبرمجة بالتدفق وفيها Node-RED بترخيص Apache-2.0، والكود أولاً وفيها Windmill بترخيص AGPLv3 مع أجزاء مملوكة، ومحركات التنسيق Temporal بترخيص MIT وKestra وAirflow بترخيص Apache-2.0، ومحركات BPMN وفيها Camunda 8 بترخيص Camunda License 1.0 الذي يحتاج ترخيصاً مدفوعاً للإنتاج وFlowable بترخيص Apache-2.0
الأدوات في العائلة نفسها تتشابه في طريقة العمل، ولكن تراخيصها تختلف كثيراً

وفي الصورة أعلاه لاحظ التالي:

  • أدوات الأتمتة المرئية: تبني فيها سير العمل Workflow من عقد جاهزة لمئات التطبيقات، وهي الأقرب لما يحتاجه فريق العمليات، وفيها المفتوح والتجاري.
  • البرمجة بالتدفق Flow-Based Programming: رسائل تمر بين عقد متصلة، وأشهرها Node-RED، وهي قوية في ربط الأجهزة والحساسات وبروتوكولات مثل MQTT.
  • الكود أولاً Code-First: تكتب كل خطوة سكربتاً بلغة حقيقية، ثم تربط السكربتات في سير عمل Workflow، مثل Windmill.
  • محركات التنسيق Orchestration: تحفظ حالة كل خطوة وتكمل بعد التوقف، أو تجدول خطوط البيانات، مثل Temporal وKestra وAirflow.
  • محركات BPMN: تنفذ عمليات مرسومة بمعيار BPMN فيها مهام بشرية وموافقات، مثل Camunda وFlowable.

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

n8n

n8n أداة أتمتة مرئية تبنيها شركة n8n GmbH الألمانية، وهي الأكثر انتشاراً بين الأدوات التي تستطيع تشغيلها على سيرفرك، وفيها مئات العقد Nodes الجاهزة، وعقدة Code تكتب فيها JavaScript أو Python عندما لا تكفي العقد Nodes، وعقد للذكاء الاصطناعي والوكلاء AI Agents. وآخر إصدار مستقر عند كتابة المقال هو 2.43.4 الصادر في 12 أكتوبر 2026، والفرع 1.x ما زال يستلم إصلاحات أمنية (آخرها 1.123.84 في 8 أكتوبر 2026).

ترخيص n8n: ماذا يسمح لك؟

الكود متاح على GitHub، ولكن ترخيصه ليس ترخيص مصادر مفتوحة بالمعنى المعتمد، وإنما Sustainable Use License الذي كتبته n8n نفسها، وتسميه الشركة «fair-code». والجملة الأساسية في قسم القيود Limitations هي:

You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.

أي أنك تستطيع استخدامه وتعديله لأغراض عمل شركتك الداخلية، أو لاستخدام شخصي أو غير تجاري، وتستطيع توزيعه للآخرين فقط مجاناً ولأغراض غير تجارية. ويضيف ملف الترخيص نفسه أن الملفات التي يحتوي اسمها على .ee. أو يحتوي اسم مجلدها على .ee ليست تحت هذا الترخيص، وإنما تحت n8n Enterprise License، الذي لا يسمح باستخدامها في الإنتاج إلا بترخيص Enterprise صالح، ويسمح بنسخها وتعديلها للتطوير والاختبار فقط. والجدول التالي يلخص ما فهمناه من النصين ومن توثيق n8n، وقد اطلعنا عليها في 14 أكتوبر 2026:

الحالةالحكمالمصدر
تثبيته على سيرفر الشركة لأتمتة عمل الشركة الداخليمسموح بالترخيص المجانيSustainable Use License
تعديل الكود لاستخدامك الداخليمسموح، مع إشعار واضح في النسخة المعدلة أنك عدلتهاقسم Notices في الترخيص
منتجك يستدعي n8n في الخلفية عبر Webhook أو API ولا يرى المستخدم واجهتهتوثيق n8n يذكر أنه متاح في كل الخطط المدفوعة تحت الترخيص القياسي دون اتفاق منفصلصفحة OEM في التوثيق
إظهار محرر n8n داخل منتجك لعملائك ليبنوا سير عمل Workflowهميحتاج اتفاق OEM تجارياً منفصلاًصفحة OEM في التوثيق
تشغيل n8n كخدمة مدفوعة لعملائك، أو بيعه، أو توزيعه بمقابلغير مسموح بالترخيص المجانيSustainable Use License
استخدام ملفات .ee (مثل SSO والبيئات) في الإنتاجيحتاج ترخيص Business أو EnterpriseLICENSE_EE.md وصفحة الإصدارات

وقد يتساءل البعض: هل يعني «الاستخدام الداخلي» أن الشركة لا تستطيع أتمتة شيء يخدم عملاءها، مثل إرسال فاتورة للعميل؟ والإجابة أن الترخيص يقيد ما تفعله بالبرنامج نفسه، فأتمتة عمليات شركتك الداخلية ولو كانت تخدم عملاءك هي غرض عمل داخلي، أما أن يصبح n8n نفسه هو ما تبيعه أو تقدمه لعملائك فهذا ما يمنعه. وإذا كانت حالتك في المنطقة الرمادية، مثل شركة تقدم خدمات أتمتة لعملاء متعددين على n8n واحد، فاسأل n8n كتابياً على [email protected] كما يذكر توثيقها، ولا تبن على تفسيرك.

ما الذي تحصل عليه مجاناً

توثيق n8n يقسم النسخ التي تستضيفها بنفسك إلى أربع، كما في صفحة مقارنة النسخ:

  • Community: مجانية، وتعمل دون مفتاح ترخيص، وفيها أغلب ميزات n8n، ومنها وضع الطابور Queue Mode.
  • Registered Community: مجانية أيضاً، تسجل بريدك فتحصل على مفتاح يفتح المجلدات Folders وتصحيح التشغيلات Executions في المحرر Debug in Editor وبيانات التشغيل المخصصة.
  • Business وEnterprise: مدفوعة، ومفتاحها يفتح ميزات الملفات .ee.

والميزات التي لا توجد في Community بحسب الصفحة نفسها هي:

  • الدخول الموحد SSO بأنواعه SAML وOIDC وLDAP.
  • مشاركة سير العمل Workflow وبيانات الاعتماد بين المستخدمين، فبدونها لا يرى سير العمل Workflow إلا مالك النسخة ومن أنشأه.
  • المشاريع Projects والبيئات Environments والتحكم بالنسخ عبر Git.
  • مخازن الأسرار الخارجية External Secrets والمتغيرات المخصصة Custom Variables.
  • بث السجلات Log Streaming إلى نظام خارجي (السجلات العادية موجودة).
  • تخزين الملفات الثنائية خارجياً External Storage، والوضع متعدد الرئيسي Multi-main.

وهذه القائمة هي ما يجعل n8n المجاني مناسباً جداً لفريق صغير أو قسم واحد، وأقل مناسبة لمؤسسة فيها عدة فرق تحتاج كل منها مساحة منفصلة ودخولاً موحداً، فهنا تحتاج Business. أما الأسعار كما ظهرت في صفحة الأسعار يوم 14 أكتوبر 2026 بالدفع السنوي فهي:

الخطةالسعر الشهري بالدفع السنويالتشغيلات Executions في الشهرالاستضافة
Starter20 يورو2,500سحابة n8n
Pro50 يورو10,000سحابة n8n
Business667 يورو40,000على سيرفرك فقط
Enterpriseبالتفاوضبالتفاوضالسحابة أو سيرفرك

ولاحظ أن n8n تحسب التشغيل Execution الواحد للسير العمل Workflow كله مهما كان عدد خطواته، وهذا مختلف عن Zapier وMake كما سنرى.

  • نقاط القوة: عدد كبير من العقد Nodes الجاهزة، وعقدة Code بلغتين، وسجل كامل لكل تشغيل، ووضع طابور Queue مجاني للتوسع، ومجتمع كبير وقوالب كثيرة، وعقد للذكاء الاصطناعي.
  • نقاط الضعف: الترخيص ليس مفتوح المصدر ويمنع تقديمه كخدمة، وأهم ميزات الفرق (SSO والمشاركة وGit) مدفوعة، وكثرة التحذيرات الأمنية في 2026 تعني أنك يجب أن تحدثه باستمرار، وهذا ما نشرحه في دليل التثبيت.

Node-RED

Node-RED بدأ في 2013 كمشروع جانبي لمهندسين في IBM، ثم أصبح مفتوح المصدر في سبتمبر من العام نفسه، وهو اليوم مشروع في مؤسسة OpenJS Foundation، وترخيص مستودعه Apache-2.0، أي ترخيص مفتوح المصدر يسمح بالاستخدام التجاري وتقديمه كخدمة دون قيد. وآخر إصدار عند كتابة المقال هو 5.0.8 الصادر في 8 أكتوبر 2026.

وطريقة العمل فيه مختلفة قليلاً عن n8n: كل عقدة تستقبل رسالة msg وتعدلها وترسلها للعقدة التالية، والمنطق غالباً في عقدة function تكتب فيها JavaScript، والتدفقات كلها تحفظ في ملف واحد flows.json يسهل وضعه في Git. ولهذا ينتشر في أتمتة المنازل والمصانع والحساسات، فهو يتعامل مع MQTT والمنافذ التسلسلية Serial وModbus بسهولة.

  • نقاط القوة: ترخيص Apache-2.0 دون أي قيد، وخفيف جداً، والتدفق ملف JSON واحد، وقوي مع الأجهزة والبروتوكولات الصناعية.
  • نقاط الضعف: لا يحفظ سجلاً لكل تشغيل كما يفعل n8n، فإذا فشلت رسالة ولم تكتب لها معالجة بنفسك ضاعت، وموصلات تطبيقات الأعمال (CRM والمحاسبة) أقل وأغلبها من المجتمع، والصلاحيات وتعدد المستخدمين محدودة في النسخة الأساسية.

Activepieces

Activepieces أداة أتمتة مرئية قريبة جداً من n8n في الشكل والفكرة، وتكتب موصلاتها Pieces بلغة TypeScript. وترخيصها مقسم كما في ملف الترخيص: كل ما هو خارج مجلدات packages/ee/ وpackages/server/api/src/app/ee تحت ترخيص MIT، وما بداخلها تحت ترخيص تجاري منفصل. وهذا يجعل النسخة المجتمعية مفتوحة المصدر فعلاً، وتستطيع تقديمها كخدمة، بخلاف n8n. وآخر إصدار عند كتابة المقال هو 0.92.3 الصادر في 12 أكتوبر 2026.

  • نقاط القوة: ترخيص MIT للنسخة المجتمعية، وواجهة بسيطة يفهمها غير المبرمج، وتثبيت ب Container واحد للتجربة.
  • نقاط الضعف: مجتمع وقوالب أصغر من n8n، ورقم الإصدار ما زال تحت 1.0 فتوقع تغييرات في الترقيات.

Windmill

Windmill يبدأ من الجهة الأخرى: تكتب كل خطوة سكربتاً بلغة حقيقية مثل Python وTypeScript وGo وBash، فيولد لها واجهة إدخال تلقائياً، ثم تربط السكربتات في سير عمل Workflow Flow أو تبني فوقها تطبيقاً داخلياً. وترخيصه يحتاج قراءة دقيقة، فملف الترخيص يذكر أن الكود المصدري تحت AGPLv3 (وأجزاء العملاء والمواصفات تحت Apache-2.0) ما عدا أجزاء المؤسسات، ولكنه يذكر أيضاً أن نسخة Community Edition الموجودة في ال Docker Images الرسمية تحتوي كوداً مملوكاً غير مفتوح، ويسمح باستخدامها مجاناً ضمن حدودها، ولا يسمح ببيعها أو تقديمها كخدمة مدارة أو تعديلها دون اتفاق. فإذا أردت نسخة مفتوحة المصدر بالكامل فعليك بناؤها من الكود دون ميزة enterprise. ومن حدود النسخة المجانية في صفحة الأسعار يوم 14 أكتوبر 2026: 50 مستخدماً كحد أقصى، و10 فقط عبر SSO، وثلاث مساحات عمل Workspaces.

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

Huginn

Huginn من أقدم أدوات الأتمتة التي تستضيفها بنفسك، مكتوب بلغة Ruby وترخيصه MIT، وفكرته «وكلاء» Agents صغيرة يراقب كل منها شيئاً (صفحة ويب أو خلاصة RSS أو بريد) ويرسل أحداثاً لوكلاء آخرين. وقد يظن البعض أنه متوقف لأنه قديم، ولكن المستودع ما زال يستلم تحديثات، وآخر إصدار فيه بتاريخ 13 أكتوبر 2026. وهو يناسب المراقبة الشخصية وجمع الأخبار والتنبيهات، أكثر من أتمتة عمليات الشركة، لأن واجهته وموصلاته لتطبيقات الأعمال محدودة مقارنة بالأدوات الأحدث.

متى تحتاج Kestra أو Temporal أو Airflow؟

هذه الأدوات ليست بديلاً مباشراً لأداة مثل n8n، وإنما تحل مشكلة أخرى ذكرناها في المقال السابق، ولكن من المفيد أن تعرف أين تقف كل منها:

الأداةالترخيصكيف تكتب سير العمل Workflowتناسب
KestraApache-2.0ملف YAML تصريحي Declarative، والمهام فيه بأي لغةخطوط بيانات ومهام مجدولة وأحداث، لفريق يريد سير العمل Workflow كملفات في Git
TemporalMITكود عادي في Go أو Java أو Python أو TypeScriptعمليات حرجة طويلة يجب أن تكمل بعد أي توقف، مثل الطلبات والمدفوعات
Apache AirflowApache-2.0رسم مهام DAG بلغة Pythonخطوط البيانات المجدولة ETL، وليس الاستجابة لحدث واحد خلال ثانية

والإصدارات الأخيرة عند كتابة المقال: Kestra 2.0.6 وTemporal 1.32.1 وAirflow 3.3.2.

Camunda وFlowable: عندما تكون العملية طويلة وفيها بشر

إذا كانت العملية تمر بعدة أشخاص وتنتظر موافقات لأيام، مثل طلب شراء أو تعيين موظف، فأنت تحتاج محرك BPMN، وهنا تغيرت الصورة في السنتين الأخيرتين:

  • Camunda 8: كوده متاح على GitHub تحت Camunda License 1.0، الذي يسمح باستخدامه في بيئات غير الإنتاج فقط، ومن الإصدار 8.6 الصادر في أكتوبر 2024 أصبحت النسخة التي تستضيفها بنفسك تحتاج ترخيصاً للإنتاج.
  • Camunda 7: كانت نسخته المجتمعية مفتوحة المصدر، ولكن الشركة أعلنت نهاية دعمها مع الإصدار 7.24 في أكتوبر 2025، والمستودع اليوم مؤرشف Archived، فلا تبدأ عليه مشروعاً جديداً.
  • Flowable: محرك BPMN مكتوب بلغة Java وترخيصه Apache-2.0، وآخر إصدار له 8.0.0 في فبراير 2026، وهو اليوم الخيار المفتوح الأوضح لمن يحتاج BPMN في الإنتاج دون ترخيص تجاري.

وسوف نعود إلى رسم العمليات بمعيار BPMN بأدوات مفتوحة في سلسلة التحول الرقمي بإذن الله.

الخدمات السحابية: Zapier وMake وPower Automate

قد لا تستضيف هذه الأدوات بنفسك، ولكنك سوف تقارن بها، وأهم ما يجب أن تفهمه فيها هو وحدة الحساب، لأن السعر الشهري وحده لا يعني شيئاً قبل أن تعرف ماذا يعد. والجدول التالي من صفحات الأسعار الرسمية كما ظهرت يوم 14 أكتوبر 2026:

الخدمةوحدة الحسابما يعد وما لا يعدأمثلة من الأسعار
Zapierالمهمة Taskكل خطوة فعل تنجح تعد مهمة، أما المشغل Trigger والأدوات المدمجة مثل Formatter وFilter وPaths فلا تعد، والخطوة الفاشلة لا تعدFree مع 100 مهمة في الشهر، وProfessional من 19.99 دولاراً شهرياً بالدفع السنوي (29.99 بالشهري) مع 750 مهمة، وTeam من 69 دولاراً بالدفع السنوي مع 2,000 مهمة
Makeالرصيد Creditأغلب الأفعال تستهلك رصيداً واحداً، وبعض ميزات الذكاء الاصطناعي أكثر، ووحدات معالجة الأخطاء والموجه Router لا تستهلكFree مع 1,000 رصيد في الشهر، ثم خطط Core وPro وTeams وEnterprise، ولم تظهر لنا أرقامها في الصفحة يوم الاطلاع فلم ننقلها
Power Automateالمستخدم أو الروبوتPremium لكل مستخدم يشمل التدفقات السحابية وRPA المرافق Attended، وProcess لكل روبوت يشمل RPA غير المرافق UnattendedPremium بسعر 15 دولاراً للمستخدم شهرياً بالدفع السنوي، وProcess بسعر 150 دولاراً للروبوت، وHosted Process بسعر 215 دولاراً مع جهاز افتراضي من Microsoft

ولكي ترى أثر وحدة الحساب، لنفرض سير عمل Workflow فيه مشغل Trigger وخمس خطوات أفعال، يعمل 3,000 مرة في الشهر، والحساب التالي من قواعد الصفحات أعلاه وليس من فاتورة حقيقية:

  • في Zapier يعد كل تشغيل خمس مهام، أي 15,000 مهمة في الشهر.
  • في Make يستهلك كل تشغيل خمسة أرصدة على الأقل، أي 15,000 رصيد أو أكثر.
  • في سحابة n8n يعد كل تشغيل تشغيلاً واحداً، أي 3,000 تشغيل، وهي داخل حد خطة Starter.
  • في Power Automate لا يتغير السعر بعدد التشغيلات Executions، وإنما بعدد المستخدمين أو الروبوتات، مع حدود استخدام لكل ترخيص.

ولا يعني هذا أن خدمة ما أرخص دائماً، فالسعر يعتمد على شكل سير عمل Workflowك، والذي يكلف حقاً في الخدمات التي تعد الخطوات هو سير العمل Workflow الذي يمر على قائمة طويلة، لأن كل عنصر في الحلقة Loop يعد خطوة.

مختبر: التدفق نفسه في ثلاث أدوات

الآن سوف نبني تدفقاً واحداً في n8n وNode-RED وActivepieces، ونرسل إليه الطلبات نفسها، ونقيس. والتدفق كما يلي:

  1. Webhook يستقبل طلب شراء فيه رقم الطلب والعميل وقائمة الأصناف بكمياتها وأسعارها.
  2. خطوة تحويل تحسب المجموع، وتجعل الأولوية high إذا كان المجموع 1000 أو أكثر، وتكتب نص الإشعار.
  3. طلب HTTP يرسل الإشعار إلى خدمة استقبال Sink كتبناها بمكتبة Python القياسية، تسجل وقت وصول كل إشعار.
  4. الرد على صاحب ال Webhook بعد وصول الإشعار، حتى تكون المقارنة عادلة بين الأدوات الثلاث.
⚠️
هذه أرقام للمقارنة بين الأدوات على الجهاز نفسه وفي الوقت نفسه، وليست أرقام أداء يمكنك أن تخطط عليها، فالجهاز كان مشغولاً بأعمال أخرى، وكل أداة تعمل بإعداداتها الافتراضية في Container واحد دون أي ضبط.

الإعداد

خدمة الاستقبال sink.py تحفظ كل إشعار مع وقت وصوله، وتستطيع أن تفشل عمداً بالرمز 503:

"""Notification sink for lab B: records every notification with its arrival time (stdlib only)."""
import json, threading, time
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
lock = threading.Lock(); got = []; fail = {"on": False}
class H(BaseHTTPRequestHandler):
    def log_message(self, *a): pass
    def send(self, code, obj):
        b = json.dumps(obj).encode(); self.send_response(code)
        self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(b)))
        self.end_headers(); self.wfile.write(b)
    def do_GET(self):
        with lock: self.send(200, got)
    def do_POST(self):
        n = int(self.headers.get("Content-Length") or 0); body = self.rfile.read(n)
        if self.path == "/reset":
            with lock: got.clear()
            return self.send(200, {})
        if self.path.startswith("/fail"):
            fail["on"] = self.path.endswith("on"); return self.send(200, fail)
        if fail["on"]:
            return self.send(503, {"error": "sink down (injected)"})
        d = json.loads(body); d["arrived"] = time.time()
        with lock: got.append(d)
        self.send(200, {"ok": True})
ThreadingHTTPServer(("0.0.0.0", 9000), H).serve_forever()

ومولد الحمل load.py يرسل الطلبات من عدة عملاء متوازيين، ثم يقرأ ما وصل إلى خدمة الاستقبال ويحسب زمن الرد والزمن الكامل من الإرسال إلى الوصول، ويتأكد أن المجموع صحيح في كل إشعار:

"""Sends N orders with C parallel clients to a webhook URL; then reads the sink and prints latency stats.
usage: python load.py <url> <n> <concurrency>"""
import json, sys, time, threading, urllib.request, urllib.error, statistics, collections, random
url, n, c = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
SINK = "http://lab-auto-sink:9000"
urllib.request.urlopen(urllib.request.Request(SINK + "/reset", b"", method="POST"))
random.seed(7)
orders = []
for i in range(n):
    items = [{"sku": f"SKU-{random.randint(1, 50)}", "qty": random.randint(1, 4), "price": random.choice([25, 80, 150, 420])} for _ in range(random.randint(1, 4))]
    orders.append({"order_id": f"ORD-{i + 1:04d}", "customer": "lab", "currency": "SAR", "items": items})
expected = {o["order_id"]: sum(i["qty"] * i["price"] for i in o["items"]) for o in orders}
lat, codes, lock, idx = [], collections.Counter(), threading.Lock(), iter(orders)
def worker():
    while True:
        with lock:
            o = next(idx, None)
        if o is None: return
        o = dict(o, ts=time.time()); t0 = time.perf_counter()
        req = urllib.request.Request(url, json.dumps(o).encode(), {"Content-Type": "application/json"}, method="POST")
        try:
            with urllib.request.urlopen(req, timeout=30) as r: code = r.status; r.read()
        except urllib.error.HTTPError as e: code = e.code
        except Exception as e: code = type(e).__name__
        dt = (time.perf_counter() - t0) * 1000
        with lock: lat.append(dt); codes[code] += 1
t0 = time.time(); ts = [threading.Thread(target=worker) for _ in range(c)]
[t.start() for t in ts]; [t.join() for t in ts]; wall = time.time() - t0
time.sleep(3)
got = json.loads(urllib.request.urlopen(SINK + "/").read())
e2e = sorted((g["arrived"] - g["ts"]) * 1000 for g in got)
wrong = sum(1 for g in got if g.get("total") != expected.get(g.get("order_id")))
q = lambda a, p: a[min(len(a) - 1, int(len(a) * p))] if a else float("nan")
lat.sort()
print(json.dumps({"sent": n, "concurrency": c, "codes": {str(k): v for k, v in codes.items()},
    "throughput_rps": round(n / wall, 1), "resp_ms_p50": round(q(lat, .5), 1), "resp_ms_p95": round(q(lat, .95), 1),
    "delivered": len(got), "unique_delivered": len({g['order_id'] for g in got}), "wrong_total": wrong,
    "e2e_ms_p50": round(q(e2e, .5), 1), "e2e_ms_p95": round(q(e2e, .95), 1)}))

وفي Node-RED التدفق ملف flows.json نضعه في مجلد البيانات قبل التشغيل:

[
  { "id": "tab1", "type": "tab", "label": "order-notify" },
  { "id": "in1", "type": "http in", "z": "tab1", "name": "POST /hook", "url": "/hook", "method": "post", "upload": false, "x": 120, "y": 80, "wires": [["fn1"]] },
  { "id": "fn1", "type": "function", "z": "tab1", "name": "transform",
    "func": "const o = msg.payload;\nconst total = o.items.reduce((s, i) => s + i.qty * i.price, 0);\nconst priority = total >= 1000 ? 'high' : 'normal';\nmsg.payload = { order_id: o.order_id, total, priority, ts: o.ts, text: `Order ${o.order_id} from ${o.customer}: ${total} ${o.currency} (${priority})` };\nmsg.headers = { 'content-type': 'application/json' };\nreturn msg;",
    "outputs": 1, "x": 300, "y": 80, "wires": [["req1"]] },
  { "id": "req1", "type": "http request", "z": "tab1", "name": "notify", "method": "POST", "ret": "obj", "url": "http://lab-auto-sink:9000/notify", "x": 470, "y": 80, "wires": [["res1"]] },
  { "id": "res1", "type": "http response", "z": "tab1", "name": "", "statusCode": "", "x": 630, "y": 80, "wires": [] }
]

وفي n8n بنينا سير العمل Workflow من ثلاث عقد: Webhook ثم Edit Fields لحساب الحقول بالتعبيرات Expressions ثم HTTP Request، واستوردناه ونشرناه بسطر الأوامر كما في مختبر المقال السابق. والتعبير الذي يحسب المجموع هو:

={{ $json.body.items.reduce((s, i) => s + i.qty * i.price, 0) }}

وفي Activepieces أنشأنا التدفق عبر واجهتها البرمجية REST API بالسكربت التالي، وفيه خطوة Code بلغة TypeScript للتحويل، وخطوة Return Response في النهاية، لأن عنوان /sync ينتظر رداً، وفي أول تجربة بدونها رد علينا بالرمز 408 رغم أن التدفق نفسه نجح ووصل الإشعار:

"""Creates the lab B flow in Activepieces through its REST API: Catch Webhook -> Code (transform) -> HTTP send_request -> Return response."""
import json, urllib.request
a = json.load(open("/tmp/ap.json")); B = "http://lab-auto-ap/api/v1"
def call(method, path, body=None):
    r = urllib.request.Request(B + path, json.dumps(body).encode() if body is not None else None,
                               {"Content-Type": "application/json", "Authorization": "Bearer " + a["tok"]}, method=method)
    try:
        with urllib.request.urlopen(r, timeout=300) as x: return json.loads(x.read() or b"{}")
    except urllib.error.HTTPError as e:
        print("HTTP", e.code, path, e.read()[:800].decode()); raise
CODE = """export const code = async (inputs) => {
  const o = inputs.order;
  const total = o.items.reduce((s, i) => s + i.qty * i.price, 0);
  const priority = total >= 1000 ? 'high' : 'normal';
  return { order_id: o.order_id, total, priority, ts: o.ts, text: `Order ${o.order_id} from ${o.customer}: ${total} ${o.currency} (${priority})` };
};"""
flow = call("POST", "/flows", {"displayName": "Order notify", "projectId": a["proj"]})
trigger = {
  "name": "trigger", "type": "PIECE_TRIGGER", "valid": True, "displayName": "Catch Webhook",
  "settings": {"pieceName": "@activepieces/piece-webhook", "pieceVersion": "0.1.42", "triggerName": "catch_webhook",
               "input": {"authType": "none"}, "propertySettings": {}},
  "nextAction": {
    "name": "step_1", "type": "CODE", "valid": True, "displayName": "Transform",
    "settings": {"sourceCode": {"code": CODE, "packageJson": "{}"}, "input": {"order": "{{trigger.body}}"}},
    "nextAction": {
      "name": "step_2", "type": "PIECE", "valid": True, "displayName": "Notify",
      "settings": {"pieceName": "@activepieces/piece-http", "pieceVersion": "0.13.0", "actionName": "send_request",
                   "input": {"method": "POST", "url": "http://lab-auto-sink:9000/notify", "headers": {}, "queryParams": {},
                             "authType": "NONE", "body_type": "json", "body": {"data": "{{step_1}}"}, "failureMode": "all"},
                   "propertySettings": {}},
      "nextAction": {
        "name": "step_3", "type": "PIECE", "valid": True, "displayName": "Return response",
        "settings": {"pieceName": "@activepieces/piece-webhook", "pieceVersion": "0.1.42", "actionName": "return_response",
                     "input": {"responseType": "json", "fields": {"status": 200, "headers": {}, "body": "{{step_2.body}}"}, "respond": "stop"},
                     "propertySettings": {}}}}}}
call("POST", f"/flows/{flow['id']}", {"type": "IMPORT_FLOW", "request": {"displayName": "Order notify", "trigger": trigger, "schemaVersion": None}})
call("POST", f"/flows/{flow['id']}", {"type": "LOCK_AND_PUBLISH", "request": {}})
f = call("GET", f"/flows/{flow['id']}")
print(flow["id"], f.get("status"), f.get("publishedVersionId") is not None)
open("/tmp/apflow", "w").write(flow["id"])

والتشغيل بالإصدارات n8nio/n8n:2.43.4 وnodered/node-red:5.0.8 وactivepieces/activepieces:0.92.3:

docker network create lab-auto-net
docker run -d --name lab-auto-sink --network lab-auto-net -v "$PWD:/lab:ro" -w /lab python:3.13.16-slim python -u sink.py
docker run -d --name lab-auto-nodered --network lab-auto-net -v "$PWD/nodered/flows.json:/data/flows.json" nodered/node-red:5.0.8
docker run -d --name lab-auto-n8n-b --network lab-auto-net -v lab-auto-n8n-b-data:/home/node/.n8n n8nio/n8n:2.43.4
docker run -d --name lab-auto-ap --network lab-auto-net -v lab-auto-ap-data:/root/.activepieces \
  -e AP_DB_TYPE=PGLITE -e AP_REDIS_TYPE=MEMORY -e AP_FRONTEND_URL=http://lab-auto-ap \
  -e AP_JWT_SECRET="$(openssl rand -hex 32)" -e AP_ENCRYPTION_KEY="$(openssl rand -hex 16)" \
  activepieces/activepieces:0.92.3

ولاحظ أننا بدأنا Activepieces أولاً بالأمر المختصر الموجود في صفحة التثبيت بـ Docker دون AP_JWT_SECRET، فتوقف العامل Worker داخلها بالخطأ التالي، والسبب أن ملف التشغيل docker-entrypoint.sh في الإصدار 0.92.3 يولد رمز العامل من AP_JWT_SECRET فقط إذا كان موجوداً:

AP_CONTAINER_TYPE: WORKER_AND_APP
Starting Activepieces (WORKER_AND_APP mode)
Error: Environment variable AP_WORKER_TOKEN is not set

النتائج

بعد عشرين طلباً للتسخين، أرسلنا لكل أداة 500 طلب من عشرة عملاء متوازيين، أداة بعد أداة والأخريان دون عمل. والمخرج كما طبعه مولد الحمل، وبعد كل أداة سطر من docker stats، وأضفنا سطر العنوان قبل كل أداة:

== nodered
{"sent": 500, "concurrency": 10, "codes": {"200": 500}, "throughput_rps": 224.6, "resp_ms_p50": 40.4, "resp_ms_p95": 68.8, "delivered": 500, "unique_delivered": 500, "wrong_total": 0, "e2e_ms_p50": 33.0, "e2e_ms_p95": 58.6}
lab-auto-nodered 0.04% 105.3MiB / 31.29GiB
== n8n
{"sent": 500, "concurrency": 10, "codes": {"200": 500}, "throughput_rps": 6.6, "resp_ms_p50": 1458.2, "resp_ms_p95": 2266.1, "delivered": 500, "unique_delivered": 500, "wrong_total": 0, "e2e_ms_p50": 1114.0, "e2e_ms_p95": 1797.0}
lab-auto-n8n-b 0.95% 496.3MiB / 31.29GiB

والذاكرة قبل الحمل، بعد أن هدأت الأدوات الثلاث:

lab-auto-nodered 0.00% 99.03MiB / 31.29GiB
lab-auto-n8n-b 2.36% 367.1MiB / 31.29GiB
lab-auto-ap 15.55% 1.724GiB / 31.29GiB

والجدول التالي يجمع الأرقام نفسها:

القياسNode-REDn8nActivepieces
الذاكرة دون عمل99 MiB367 MiB1.72 GiB
الطلبات في الثانية224.66.63.7
زمن الرد (p50 ثم p95) بالملي ثانية40 ثم 691458 ثم 22662660 ثم 4579
الإشعارات الواصلة والصحيحة500 من 500500 من 500500 من 500
حجم ال Image كما يعرضه docker images1.03GB1.79GB2.15GB

وفي النتائج أعلاه لاحظ التالي:

  • الفرق بين Node-RED والأداتين الأخريين كبير جداً، والسبب أن Node-RED يمرر الرسالة في الذاكرة بين العقد Nodes ولا يحفظ شيئاً عن التشغيل، بينما n8n وActivepieces يسجلان كل تشغيل بمدخلاته ومخرجاته، ويشغلان التعبيرات والكود داخل بيئة معزولة Sandbox.
  • أردنا أن نعرف من أين يأتي البطء في n8n، فأعدنا التشغيل مرتين للتشخيص فقط: مرة دون حفظ التشغيلات Executions الناجحة بالمتغير EXECUTIONS_DATA_SAVE_ON_SUCCESS=none فلم يتحسن (4.4 طلب في الثانية)، ومرة بمحرك التعبيرات القديم N8N_EXPRESSION_ENGINE=legacy فارتفع إلى 11.0 طلباً في الثانية، وكان المعالج أثناءه عند 104% أي نواة واحدة كاملة. أي أن جزءاً كبيراً من الوقت في عزل التعبيرات، وهو العزل الذي أصبح افتراضياً من الإصدار 2.35.0 كما في توثيق محرك التعبيرات، ولا ننصح بالعودة إلى legacy لأنه يشغل التعبيرات دون عزل.
  • n8n في هذا الوضع عملية واحدة، فإذا احتجت أكثر فالحل وضع الطابور Queue Mode وعدة Workers، وهذا ما نجربه في دليل التثبيت.
  • ست طلبات في الثانية تعني أكثر من 500 ألف تشغيل في اليوم إذا كان الحمل متساوياً، وهذا يكفي أغلب أعمال الأتمتة الداخلية، فلا تختر Node-RED لأنه الأسرع إذا كنت تحتاج سجل التشغيلات Executions وإعادة تشغيل ما فشل.

ماذا يحدث عندما يفشل الإشعار؟

جعلنا خدمة الاستقبال ترد بالرمز 503 على كل طلب، وأرسلنا عشرة طلبات لكل أداة، والمخرج مختصر من مولد الحمل:

== nodered (sink returns 503)
{'sent': 10, 'codes': {'503': 10}, 'delivered': 0}
== n8n (sink returns 503)
{'sent': 10, 'codes': {'500': 10}, 'delivered': 0}
== activepieces (sink returns 503)
{'sent': 10, 'codes': {'500': 10}, 'delivered': 0}

ثم سألنا كل أداة عما حفظته:

  • Node-RED: مرر الرمز 503 كما هو إلى المرسل، ولم يكتب أي سطر خطأ في سجله، فإذا لم يعد المرسل المحاولة ضاع الطلب دون أثر، إلا إذا أضفت بنفسك عقدة catch تحفظه في مكان ما.
  • n8n: رد بالرمز 500، وحفظ التشغيلات Executions العشرة بحالة error ومعها بيانات الطلب، فتستطيع مراجعتها وإعادة تشغيلها.
  • Activepieces: رد بالرمز 500، وظهرت في قائمة التشغيلات Executions عشرة تشغيلات بحالة FAILED.

وهذا هو الفرق الحقيقي بين العائلتين: Node-RED أداة تدفق سريعة تكتب أنت حولها ما تحتاجه من سجل ومعالجة، وn8n وActivepieces منصات أتمتة تعطيك هذا السجل جاهزاً وتدفع ثمنه في السرعة والذاكرة.

الواجهات كما ظهرت في المختبر

الصور التالية من نسخنا في المختبر نفسه، وتبين التدفق في كل أداة:

محرر n8n وفيه سير العمل Order notify بثلاث عقد متصلة من اليسار إلى اليمين: Webhook ثم Transform ثم Notify، وفي الأعلى زر Published وتبويبات Editor وExecutions
سير العمل Workflow في n8n 2.43.4، وتبويب Executions يعرض سجل كل تشغيل
محرر Node-RED وفيه تبويب order-notify بأربع عقد: POST /hook ثم transform ثم notify ثم http، وعلى اليسار لوحة العقد Palette وعلى اليمين لوحة المعلومات والرسائل
التدفق نفسه في Node-RED 5.0.8، وهو محفوظ في ملف flows.json واحد
محرر Activepieces وفيه أربع خطوات من الأعلى إلى الأسفل: Catch Webhook ثم Transform من نوع Code ثم Notify من نوع HTTP ثم Return response، وعلى اليمين إعدادات ال Webhook ورابطه وملاحظة أن الطلب المتزامن يعيد 408 بعد ثلاثين ثانية
التدفق في Activepieces 0.92.3، ولاحظ ملاحظة الطلبات المتزامنة على اليمين

حذف المختبر

docker rm -f lab-auto-ap lab-auto-n8n-b lab-auto-nodered lab-auto-sink
docker volume rm lab-auto-ap-data lab-auto-n8n-b-data
docker network rm lab-auto-net

أي أداة تختار؟

الجدول التالي يلخص ما سبق:

حالتكالأنسبوالسبب
فريق عمليات أو تقنية يربط تطبيقات الشركة الداخليةn8nأكبر عدد من العقد Nodes الجاهزة وسجل كامل للتشغيلات، والترخيص يسمح بالاستخدام الداخلي مجاناً
تريد تقديم الأتمتة لعملائك أو تضمينها في منتجكActivepieces، أو Windmill مبنياً من الكودترخيص MIT أو AGPLv3 يسمح بذلك، أما n8n فيحتاج اتفاقاً تجارياً
أجهزة وحساسات وMQTT وأتمتة منزل أو مصنعNode-REDخفيف وسريع وترخيصه Apache-2.0، وقوي مع البروتوكولات الصناعية
فريق مطورين يريد كل خطوة كوداً حقيقياًWindmill أو Kestraالسكربتات والملفات تبقى في Git وتختبر كأي كود
عملية مالية أو حرجة يجب أن تكمل بعد أي توقفTemporalالتنفيذ الدائم Durable Execution هو هدفه الأساسي
عملية طويلة فيها موافقات ومهام بشريةFlowable، أو Camunda 8 بترخيص مدفوعتنفيذ BPMN بمهام بشرية، وانتبه إلى ترخيص Camunda للإنتاج
فريق صغير دون أحد يدير السيرفراتZapier أو Make أو سحابة n8nلا يوجد ما تشغله، وراقب وحدة الحساب قبل أن يكبر الاستخدام
مؤسسة تعمل بالكامل على Microsoft 365Power Automateمدمج مع حساباتها وصلاحياتها، والسعر بالمستخدم وليس بالتشغيل

أخطاء شائعة

  • الاعتماد على وصف «مفتوح المصدر» في صفحة المنتج: اقرأ ملف الترخيص في المستودع، فكما رأينا الكود المتاح ليس دائماً مفتوح المصدر، والنسخة في ال Docker Image قد تختلف عن الكود.
  • مقارنة الأسعار بالرقم الشهري: المهمة في Zapier ليست التشغيل في n8n، فاحسب على شكل سير عمل Workflowك الحقيقي.
  • اختيار الأداة الأسرع دون النظر إلى السجل: الأتمتة التي لا تحفظ ما فشل تحتاج أن تبني لها ذلك بنفسك.
  • البدء بنسخة Community لفريق كبير: ثم اكتشاف أن المشاركة والدخول الموحد مدفوعان بعد أن بنى كل شخص سير عمل Workflowه في حسابه.

الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

  • n8n يسمح بالاستخدام الداخلي مجاناً ويمنع تقديمه كخدمة، وميزات الفرق مثل SSO والمشاركة وGit مدفوعة.
  • Node-RED بترخيص Apache-2.0 كان الأسرع والأخف في مختبرنا، ولكنه لا يحفظ سجلاً للتشغيلات، ولم يترك أثراً للطلبات الفاشلة.
  • Activepieces بترخيص MIT للنسخة المجتمعية بديل قريب من n8n إذا كان الترخيص يهمك، وكان الأثقل في الذاكرة.
  • Windmill وKestra للمطورين، وTemporal للعمل الحرج الطويل، وFlowable لعمليات BPMN دون ترخيص تجاري.
  • الخدمات السحابية تحسب بوحدات مختلفة، فاحسب التكلفة على شكل سير عمل Workflowك.

وفي المقال القادم بإذن الله سوف نثبت n8n للإنتاج مع PostgreSQL ووضع الطابور Queue Mode وعدة Workers، ونجرب ماذا يحدث إذا فقدت مفتاح التشفير.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال، والاطلاع على التراخيص وصفحات الأسعار بتاريخ 14 أكتوبر 2026، وتشغيل المختبر على n8n 2.43.4 وNode-RED 5.0.8 وActivepieces 0.92.3.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.