في المقال السابق ذكرنا أن هناك خمس طرق لربط الأنظمة، وأن أغلب الربط الداخلي بين تطبيقات الشركة يناسبه نوع من الأدوات يسمى أدوات الأتمتة المرئية، أو iPaaS، والسؤال التالي الطبيعي هو: أي أداة؟ وهنا تكتشف أن عدد الأدوات كبير، وأن كثيراً منها يوصف بأنه «مفتوح المصدر» وهو ليس كذلك بالمعنى المعتمد، وأن الأدوات السحابية تحسب السعر بطرق مختلفة تجعل المقارنة بينها صعبة.
المواضيع في السلسلة:
- أتمتة سير العمل Workflow: متى تكتب كوداً ومتى تستخدم أداة؟
- أدوات أتمتة سير العمل Workflow: n8n وNode-RED وبدائلهما (الموضوع الحالي)
- تثبيت n8n على سيرفرك للإنتاج بطريقة آمنة
- سير عمل حقيقي في n8n: من التذكرة إلى تسليم الجهاز
- وكيل ذكي AI Agent في n8n بنموذج محلي وموافقة بشرية
وسوف نناقش في هذا المقال ما يلي:
- عائلات أدوات الأتمتة، وأين تقع كل أداة.
- n8n وترخيصه Sustainable Use License: ماذا يسمح لك أن تفعل على سيرفرك، وما الذي يحتاج ترخيصاً مدفوعاً.
- Node-RED وActivepieces وWindmill وHuginn، ثم Kestra وTemporal وAirflow، ثم محركات BPMN مثل Camunda وFlowable.
- الخدمات السحابية Zapier وMake وPower Automate، وكيف يحسب كل منها السعر.
- مختبر: التدفق نفسه (Webhook ثم تحويل ثم إشعار) مبنياً في ثلاث أدوات، مع قياس الذاكرة والسرعة وما يحدث عند الفشل.
- جدول قرار يساعدك على الاختيار.
خمس عائلات من الأدوات
الأدوات التي تسمعها في هذا المجال لا تحل المشكلة نفسها، والصورة التالية تجمعها في خمس عائلات مع ترخيص كل أداة:

وفي الصورة أعلاه لاحظ التالي:
- أدوات الأتمتة المرئية: تبني فيها سير العمل 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 أو Enterprise | LICENSE_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 في الشهر | الاستضافة |
|---|---|---|---|
| Starter | 20 يورو | 2,500 | سحابة n8n |
| Pro | 50 يورو | 10,000 | سحابة n8n |
| Business | 667 يورو | 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 | تناسب |
|---|---|---|---|
| Kestra | Apache-2.0 | ملف YAML تصريحي Declarative، والمهام فيه بأي لغة | خطوط بيانات ومهام مجدولة وأحداث، لفريق يريد سير العمل Workflow كملفات في Git |
| Temporal | MIT | كود عادي في Go أو Java أو Python أو TypeScript | عمليات حرجة طويلة يجب أن تكمل بعد أي توقف، مثل الطلبات والمدفوعات |
| Apache Airflow | Apache-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 غير المرافق Unattended | Premium بسعر 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، ونرسل إليه الطلبات نفسها، ونقيس. والتدفق كما يلي:
- Webhook يستقبل طلب شراء فيه رقم الطلب والعميل وقائمة الأصناف بكمياتها وأسعارها.
- خطوة تحويل تحسب المجموع، وتجعل الأولوية
highإذا كان المجموع 1000 أو أكثر، وتكتب نص الإشعار. - طلب HTTP يرسل الإشعار إلى خدمة استقبال Sink كتبناها بمكتبة Python القياسية، تسجل وقت وصول كل إشعار.
- الرد على صاحب ال Webhook بعد وصول الإشعار، حتى تكون المقارنة عادلة بين الأدوات الثلاث.
الإعداد
خدمة الاستقبال 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-RED | n8n | Activepieces |
|---|---|---|---|
| الذاكرة دون عمل | 99 MiB | 367 MiB | 1.72 GiB |
| الطلبات في الثانية | 224.6 | 6.6 | 3.7 |
| زمن الرد (p50 ثم p95) بالملي ثانية | 40 ثم 69 | 1458 ثم 2266 | 2660 ثم 4579 |
| الإشعارات الواصلة والصحيحة | 500 من 500 | 500 من 500 | 500 من 500 |
| حجم ال Image كما يعرضه docker images | 1.03GB | 1.79GB | 2.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 منصات أتمتة تعطيك هذا السجل جاهزاً وتدفع ثمنه في السرعة والذاكرة.
الواجهات كما ظهرت في المختبر
الصور التالية من نسخنا في المختبر نفسه، وتبين التدفق في كل أداة:



حذف المختبر
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 365 | Power 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.