PHP أم Node.js أم Go؟ مقارنة شاملة لاختيار التقنية الأنسب لمشروعك

مقدمة

هذا المقال مختلف قليلاً عن أي مقال سابق، حيث نستضيف حوارًا تقنيًا عميقًا حول لغة PHP، وتحديداً إطار العمل Laravel، وسنتناول أيضاً Node.js و Go. سنقوم بتحليل هذه التقنيات الثلاث بهدوء وندخل في أبعادها وتداعياتها المختلفة.

للتنبيه، هذا المقال دسم ويحتوي على نقاشات تقنية عميقة، لذا ننصح بالقراءة بتركيز وهدوء. الكلام الذي ورد فيه كثير ودسم وهو نتيجة نقاشات مستفيضة مع المهندس محمد أسامة، وهو مدير هندسة أول في شركة “يسير” حالياً. لا يركز محمد على كونه مديرًا هندسيًا ومديرًا للأفراد فحسب، بل يحرص دائمًا على الجانب التقني أن يكون متمكناً ومتقدماً فيه. قام بالانتقال من PHP إلى عالم Node.js ثم إلى عالم Go، لذا يمتلك خبرة في هذه التقنيات الثلاث ويحب دراسة الأمور وتحليلها بالتفصيل.

في هذا المقال، سيقدم لك المصطلح والفكرة وكل ما تحتاج لمعرفته عن هذه التقنيات، كما سنتعرف في النهاية على محمد والأبعاد الإدارية التي يدير بها عمليته الإدارية.

حوار مع محمد أسامة

مرحباً أسامة، كيف حالك؟

أنا بخير، كل شيء على ما يرام.

أنت تعمل كمدير هندسي اليوم، ولديك خبرة عملية في مشاريع عملت فيها بـ PHP (Laravel)، و Go، و Node.js. السؤال المبتذل هو: ما هي أفضل لغة برمجة من بينها؟

دائماً يقولون إن الأمر “يعتمد”. يعتمد على السياق الذي نحن فيه.

لنبَدأ من أسس عملية حتى لا يكون الموضوع مفتوحاً بشكل مبتذل. عملياً، نحن متفقون على أنه لا يوجد شيء اسمه “أفضل لغة برمجة” بشكل عام. لكن لنتحدث من منظور الواجهة الخلفية (Backend) كشيء يعتمد على الأداء العالي (High Performance). لو كنت سأبني خدمة تداول (Trading Service) تتضمن الكثير من العمل الحقيقي، وأمامي خيار بناء هذه الخدمة بـ PHP، أو JavaScript (Node.js)، أو Go، كيف سيكون اختياري؟

الأمر يعتمد أولاً على مدى ملاءمة المنتج للسوق (Product-Market Fit). ما هي الميزات التي سنضعها في المنتج؟ ما هي الكفاءات (Calibers) المتوفرة لدينا؟ هذا من العوامل الرئيسية في مساهمتي. متى نريد إطلاق المنتج؟ هذا أيضاً من ضمن العوامل المهمة.

متى نريد إطلاق المنتج؟

نعم، متى نريد إطلاق المنتج؟ لأن منحنى التطوير (Development Curve) يستغرق وقتاً، وحتى منحنى التوظيف (Hiring Curve) يستغرق وقتاً إذا أردنا توظيف مهندسين. نحن هنا لم نتحدث تقنياً بعد، بل على مستوى المنتج. متى نريد إطلاقه؟ على مستوى العمل (Business) أيضاً، الأمر يعتمد.

على المستوى التقني، الأمر يعتمد أيضاً. على سبيل المثال، لا يمكنني أن أقول إنني سأطلق شيئاً باستخدام حزمة تقنيات فاخرة (Fancy Stack) مثل “لنعمل بـ Go” وما إلى ذلك، بينما المشروع لن يخدم كمية كبيرة من الزيارات (Traffic). لكن في تصوري، لو كان الأداء عالياً وسيخدم زيارات كثيرة، فهذه هي حالة الاستخدام التي سأضعها.

لنفترض أنه ليس كل شيء في النظام لديه زيارات عالية. على سبيل المثال، لوحة تحكم المسؤول (Admin Dashboard) أو لوحة تحكم المستخدم (User Dashboard)، هذه الأشياء يمكننا القول إنها أساسية، أو يمكننا تنفيذها بأي لغة. لا توجد لغة ستشتكي لو نفذتها مثلاً بـ Laravel (PHP) أو Node.js (سواء Nest أو Express). هذه الأشياء لا تحتاج إلى كثافة عالية (High Intensity).

تقصد أن معظمها عمليات CRUD (Create, Read, Update, Delete) بسيطة، لذا لا تهم اللغة التي سنعمل بها؟

بالضبط. في هذه الحالة، لنختر الأشياء الأساسية أو التي نستخدمها في عملنا اليومي. لا تحتاج إلى هندسة معقدة أو مواهب عالية جداً لتنفيذها.

أما بالنسبة للأشياء مثل تبادل البيانات (Exchanging) أو التداول (Trading) نفسه، حيث توجد استدعاءات كثيرة نعمل عليها، في هذه الحالة نعم، يمكننا العمل بـ Go. ليس في كل شيء، لأن الأمر يعتمد أيضاً. على سبيل المثال، التزامن (Concurrency) مهم جداً. إذا كنت تتحدث عن التداول، فهناك تزامن كثير وأحداث كثيرة تحدث في نفس الوقت. هذه الأشياء لن تعمل جيداً مع PHP. يمكننا النقاش حول ما إذا كانت ستعمل مع Node أم لا، لكن Go ستكون هي الرابحة.

خلاصة القول السريعة هي أن Go ستكون الخيار الأفضل في هذه الحالة. فيما عدا ذلك، لا. حتى فكرة Go نفسها عندما يتعامل معها المجتمع البرمجي، فإنه يتبناها للأشياء التي هي خدمات مصغرة (Microservices). لدي ميزة أريدها بأداء عالٍ جداً، فأذهب لاستخدام Go من أجل هذه الجزئية وأترك باقي التطبيق.

هل لو بنيت التطبيق كله بـ Go ستشتكي اللغة؟ لن تشتكي. هل ستكون لدي مشكلة في الأداء؟ أيضاً لا. لكن المشكلة تكمن في المجتمع البرمجي نفسه، طريقة تعامله مع اللغة وفي أي منطقة يضعها. نحن نضع اللغة في منطقة أنها الأفضل في التزامن. حتى من تعريفها، هي “First-class concurrency”.

“First-class concurrency”؟

بالضبط. هي من الدرجة الأولى في التزامن. فإذا كنت ستفعل شيئاً يتطلب التزامن، فبالتأكيد ستعمل بـ Go. يمكننا النقاش حول ما إذا كنا سنعمل بـ Rust أم لا، لكن مرة أخرى، هذا ليس العامل الوحيد الحاسم. لدي أيضاً عوامل تساهم في السوق، مثل توفر المرشحين للتوظيف. لو غادر أحدهم، كم سيستغرق الأمر لنجد بديلاً؟ منحنى التعلم (Learning Curve) للحاجة، مدى خبرة المطور بها.

قد يكون الجميع يعمل بـ Go. على سبيل المثال، لو عملت اليوم بـ Node.js لمدة أربع أو خمس سنوات، ثم انتقلت إلى Go، في غضون ثلاثة أو أربعة أيام سأتمكن من بناء تطبيق CRUD عادي. لأن لدي بالفعل خبرة مشتركة في بناء تطبيقات CRUD بلغة أخرى.

لأنك جربت CRUD بلغة أخرى، فالانتقال إلى لغة جديدة ليس صعباً؟

بالضبط. الموضوع ليس صعباً. لكن عندما ندخل في تفاصيل أعمق، على سبيل المثال، جلست مع شخص متخصص في Node.js وتحدثنا عن عقلية Go الأساسية، مثل أنماط التزامن fan-in و fan-out، والفرق بين goroutines و channels، ومتى نستخدم wait group. كشخص قادم من خلفية Node.js، سأكون في حيرة من أمري. لا أعرف هذه الأشياء.

هذه هي النقطة التي أعتقد أنها تسمى “النموذج” (Paradigm) أو رؤية استخدام اللغة نفسها. لكنني أرى أيضاً أنها في النهاية تعود إلى أصول أنك درست جيداً، وليس لها علاقة باللغة كأداة.

الفكرة أيضاً، لنكن متفقين، كم شخصاً على أرض الواقع أخذ دورة مثل CS50 والأشياء الجميلة المشابهة قبل أن يبدأ باللغة التي سيعمل بها؟ هذا ليس الواقع. كلنا نبدأ بـ “لنأخذ دورة في ثلاث ساعات”، ثم “لنقم ببناء تطبيق CRUD”، ثم “لنجد وظيفة”. للأسف، نسبة كبيرة من السوق تعمل بهذه الطريقة. أنا لا أقول إن هذا خطأ، لا مشكلة في أن تجد مكانك في السوق، لكن ماذا بعد؟ هل ستستمر على هذا المنوال أم سترتقي وتتعلم ما فاتك؟ من خلال المقابلات الكثيرة التي أجريتها، الكثير من الناس لا يزالون يفتقرون إلى الأساسيات.

لا أعترض على أن تبدأ بدورة سريعة وتبني مشروعاً أو اثنين لتجد مكانك في السوق كـ “مبتدئ” (Junior). لكن بعد أن تجد مكانك، تعلم لتتمكن من البقاء في السوق.

إذاً، لو كنت تقول لي إنني سأعرف هذه المفاهيم في Node، فالأغلب أنني لن أعرفها لأنني لم أحتك بها في اللغة أثناء عملي.

PHP أم Go: مقارنة الأداء والتزامن

لنرجع خطوة للوراء لنضع بعض المسلمات. الإجابة التي استخلصتها هي أنه في حالة التزامن العالي والأداء الفائق وملايين المستخدمين، فإن الإجابة البديهية هي Go. لكن فكرتي هي أنني لو قمت بنفس المشروع باستخدام PHP، فإنه سيعمل، قد يكون أبطأ، ولكن مع تطور PHP اليوم، سيدخل الكثيرون في نقاش حول هذا الأمر.

هو سيعمل، لكن لنقُل إننا متفقون وسنتجه إلى هذه النقطة، تطور PHP اليوم…

أنا لست ضد PHP على الإطلاق. آخر مرة عملت فيها بـ PHP كانت قبل أربع أو خمس سنوات، وحتى اليوم ما زلت أحترم Laravel وأحبها.

هذه عبارة قوية جداً.

وأريد أن أقول لك، نفس المثال ينطبق على React. عملت بـ React كثيراً، وفي البداية كنت أعمل بـ Vue. سبت Vue وعملت بـ React كثيراً وساهمت حتى في GraphQL نفسها. لكن عندما عدت إلى Vue، كنت أقدر بساطتها. عندما عدت من React إلى Vue مرة أخرى، كنت أتساءل أين هي الأدوات التي كنا نستخدمها. على سبيل-مثال، في Vue، أقصى ما يمكنك فعله هو استخدام computed property وهذا هو التخزين المؤقت (Caching) لديك. في React، لديك تحكم أكبر، مثل استخدام useMemo للتحكم في الخصائص داخل الكائن للتأثير على مرحلة العرض (Rendering Phase) وتجنب إعادة العرض غير الضرورية. لم يكن لديك هذا التحكم في Vue، لكن في Vue 3، أصبحنا نسخة من React.

إذاً، مشكلتك مع اختيار اللغة ليست أن PHP بطيئة و Go أسرع. نحن نتحدث عن الأداء العالي والتزامن، وهنا سيفوز Go. لكن لو ذهبت إلى PHP، سأجد أيضاً تزامنًا. هذا هو السؤال البديل: أليس هناك في PHP ما يسمى Octane الذي يمكنك من خلاله تحقيق نوع من التزامن؟

هذه النقطة جدلية بعض الشيء. اليوم، عندما نعمل بـ Octane، ماذا فعلنا؟ أنشأنا خادم HTTP يقبل طلبات كثيرة بشكل متزامن باستخدام Go. صحيح، لقد أخذنا الطلب، ولكن من سيعمل به بعد ذلك؟ PHP، التي في النها-ية هي لغة مفسرة (Interpreted) سطراً بسطر. لو أخذت مهمة ثقيلة، سنبقى ننتظر حتى تنتهي. حتى لو جعلتها عالية الأداء من الأعلى، فالأمر أشبه بمن “خدعوك فقالوا”. من الأعلى أداء عالٍ، لكن من الأسفل ما زلنا نعمل بـ PHP كما نحن.

أنا لست ضد PHP. أقوى نظام بيئي (Ecosystem) بالنسبة لي، من خلال كل خبرتي ولغات البرمجة المختلفة، لم أجد نظاماً بيئياً أقوى من Laravel.

لأنه بارع في إنتاجية المبرمج، لكنه قد يفشل في بعض الجوانب.

عقلية Laravel ونظامها البيئي مختلفان تماماً وصلبان جداً بشكل مخيف مقارنة بغيرهما. على سبيل المثال، عندما كنت أعمل بـ Laravel وانتقلنا للعمل بـ Nest.js، أقرب شيء لـ Laravel هو Nest.js. عندما بدأنا العمل به، أردت أن أفعل شيئاً مثل Service Provider الموجود في Laravel. في Nest، لدينا Providers و Service Container مشابه. لدينا Hooks للنماذج (Models) في Laravel، لكن في Nest لا يوجد Hooks، بل Service Providers يمكن أن تعمل كـ Hooks بطريقة ما.

عندما نتحدث عن ORM (Object-Relational Mapping)، من يناظر ORM الخاص بـ Laravel؟ Sequelize مثلاً؟ لا. TypeORM بالنسبة لي مشكلة كبيرة. أنا ضد TypeORM بشدة. في إحدى الشركات، عملنا به لمدة شهر واضطررنا إلى التراجع عن كل شيء والعودة إلى Sequelize.

هل كان ذلك في الإصدار الأول منه؟

لا، هذا الكلام كان قبل سنتين تقريباً. هو يتعامل معك كأنك مهندس SQL. تريد عمل JOIN، فتكتب LEFT JOIN كأنك تكتب SQL وتحدد الأعمدة. إذا كنت سأفعل ذلك، فلماذا أستخدم ORM من الأساس؟ أكتب SQL مباشرة.

عند التعامل مع الاستعلامات المعقدة (Complex Queries)، تجد نفسك تكتبها يدوياً. لو أردت عمل Nested Joins، لا يفهمها جيداً. بالنسبة لـ TypeORM، Sequelize هو الإطار الأكثر نضجاً لكتابة SQL وقد أراحني. لكنه لا يزال لا يقارن بـ Eloquent (الـ ORM الخاص بـ Laravel).

لدينا مشاكل كثيرة. على سبيل المثال، طريقة كتابة الـ ORM في Laravel مختلفة تماماً. في Sequelize، يمكنني عمل include داخل include داخل include. ثم عليك أن تحدد ما إذا كان هذا الاستعلام subquery أم لا. كمهندس واجهة خلفية، ما الفرق بالنسبة لي؟ لا أعرف. في إحدى المرات، لم يعمل الاستعلام لأن JOIN لم يكن subquery، مما أدى إلى بيانات غير صحيحة. هل يفترض بي أن أعرف ذلك؟

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

بالإضافة إلى عدم الاتساق (Inconsistency). أحياناً، بعد عمل relation وجلب attribute، تجده undefined، ثم في لحظة أخرى تجده موجوداً. لن أظل أبحث في أعماق هذه المشاكل. بصراحة، هذه مشكلة مجتمع JavaScript. نحن نصنع أشياء تبدو غير ناضجة. نحن نصنع سحراً.

ملاحظة: هناك حزمة على npm تسمى thanos، تقوم بحذف نصف الكود. لماذا يفعل مطور شيئاً كهذا؟ كمجتمع، يجب أن نكون أكثر نضجاً ونقدم شيئاً يخدم حاجة حقيقية.

لدي تعقيب على كلامك. أنا شخصياً كنت أقول للناس “ما هذا الصداع مع PHP؟”. كنت أعمل بـ PHP الخام. ثم جربت Laravel وانبهرت بمستوى التوثيق والرقي في الحفاظ على معيار معين يتبعه الناس. هو يعطيك آراء قوية وقوة. لم أر قوة مثل قوة Nest و Laravel في التوثيق. هذا ما شجعني على عمل دورة عنه.

إذاً، يمكننا الخروج بخلاصة بسيطة: متى يمكنني الذهاب إلى PHP (Laravel) ومتى يمكنني الذهاب إلى Go؟

متى تختار Laravel أو Nest.js أو Go؟

الأمر يعتمد على حالات الاستخدام. في إحدى الشركات، أردنا إعادة بناء تطبيقنا المكتوب بـ PHP الأصلي. كان الفريق يعمل بـ PHP، وكان القرار بيدي. كان من الطبيعي أن نختار Laravel، لكنني اخترت Nest.js. لماذا؟

كانت رؤيتنا هي التوجه نحو بنية قائمة على الأحداث (Event-Driven Architecture)، حيث تكون كل الوحدات (Modules) تتحدث مع بعضها عبر الأحداث. كنا نتجه نحو نمط CQRS (Command Query Responsibility Segregation).

ما هو CQRS؟ هو اختصار لـ Command Query Responsibility Segregation. ببساطة، نحن نفصل بين استعلامات القراءة (Queries) وأوامر الكتابة (Commands). هذا النمط يسير جنباً إلى جنب مع البنية القائمة على الأحداث.

عندما بحثت في Laravel عن كيفية تطبيق CQRS والأحداث، كان الأمر صعباً. مرة أخرى، توجه المجتمع البرمجي مهم. كان CQRS متبنى بشكل كبير في Nest.js، بينما في Laravel كان موجوداً كحزمة لكنها كانت لا تزال ضعيفة.

إذاً، لو عدنا للسؤال، هل نختار Laravel أم Nest.js؟

الأمر يعتمد على أشياء أساسية. هل التطبيق يعمل في الوقت الفعلي (Real-time)؟ هل هو كثيف في عمليات الإدخال والإخراج (I/O)؟ (وهذا يشمل قواعد البيانات، الطلبات، الملفات، إلخ). إذا كان يتعامل مع ملفات كثيرة، قواعد بيانات كثيرة، طلبات كثيرة، فإن Node.js جيد في هذه النقطة لأنه مصمم ليكون غير حاجز لعمليات الإدخال والإخراج (Non-blocking I/O).

أما PHP، فهي ليست جيدة في هذه النقطة لأنها مفسرة سطراً بسطر وتنتظر. يمكنك استخدام Coroutines في PHP لتحقيق التزامن، لكنني أتحدث عن أصل اللغة، وليس الإضافات المجتمعية. PHP صُممت لمعالجة الطلبات (Process a request) ثم الانتهاء. إذا كنت تعتمد على تشغيل أشياء في الخلفية، فهذا ليس تصميمها الأصلي.

لذلك، نلجأ إلى حلول مثل قوائم الانتظار (Queues) في Laravel. نضع المهمة في قائمة انتظار، وهناك عامل (Worker) يأخذها ويعالجها. لكن هذا ليس من صلب اللغة. أنت تستخدم أداة خارجية لتسهيل هذه الوظيفة.

إذاً، PHP مناسبة جداً لأي شيء يخدم طلبات منطقية، مثل تطبيقات CRUD القياسية؟

بالضبط. ستعمل بشكل جيد جداً.

إذاً، Nest أم Laravel؟

بالنسبة لي، Laravel ليس فقط للأشياء الأساسية. هناك تطبيقات ضخمة مبنية بـ Laravel. لا يمكننا أن نقول إنها أساسية، فهذا ظلم لـ Laravel و PHP. هي قابلة للتوسع (Scalable) جداً، لكن الأمر يعتمد على حالة الاستخدام التجارية (Business Use Case). فيسبوك يعمل بـ PHP. لا يمكننا القول إنها غير قابلة للتوسع.

من منظور عمليات التطوير (DevOps)، الثلاثة متماثلون تقريباً. قد يختلف استهلاك الذاكرة، لكن فرق بضعة غيغابايت من الرام لن يكلف الشركة الكثير في النهاية.

لكن هناك فرق في الأداء، زمن الاستجابة (Latency)، استهلاك الذاكرة، معالجة القمامة (Garbage Collection).

نعم، هناك فرق بالتأكيد. لكن القصد هو كيف يترجم هذا الفرق إلى أموال في النهاية. إلا إذا كنت تتحدث عن حالة استثنائية جداً. كانت هناك شركة غيرت لغتها بالكامل إلى Go لأن جامع القمامة في Go يعمل في الخلفية ولا يوقف العالم (Stop-the-world).

ما هو “Stop-the-world”؟ عندما يعمل جامع القمامة في بعض اللغات، فإنه يوقف التطبيق بالكامل ليمسح الذاكرة غير المستخدمة. في Go، هذه العملية تحدث في الخلفية دون إيقاف التطبيق.

الشركة وصلت إلى مرحلة أن لديها مشكلة مع اللغة نفسها، فغيرتها.

إذاً، القرار يعتمد على حالة العمل. إذا كانت حالة العمل يمكن تنفيذها بأي من اللغات الثلاث، فسنختار بناءً على الموارد المتاحة، استقرار التقنية، وعدم التحيز.

بالنسبة لي، Go هي خياري الأخير. أضعها في منطقة الأشياء المعقدة جداً. أنا أدخل لعمل ميزة معقدة وأخرج.

لماذا؟

لأن مجتمعها ناضج وصلب. على سبيل المثال، واجهتنا مشكلة عند تحويل تطبيقنا إلى SaaS في Nest.js. كنا نعمل بنظام CQRS، وأردنا تتبع الأحداث وتغيير المشغل (Driver). كانت هناك مشكلة أن الطلب بمجرد دخوله إلى مسار الأوامر أو الاستعلامات، يختفي في الخلفية. لم نتمكن من الحصول على استجابة. هذه المشكلة موجودة في CQRS منذ 2018. استغرقنا شهرين إلى ثلاثة أشهر من البحث والتطوير وتعديل الحزم لحل هذه المشكلة في Nest. في PHP، كان من الممكن حلها في يومين.

إذاً، الخلاصة هي أن حالة الاستخدام هي التي تحكم. لا توجد إجابة عامة سطحية بأن لغة أفضل من لغة.

هل نمزج التقنيات في الخدمات المصغرة؟

هل من المنطقي أن أمزج التقنيات؟ أن أعمل خدمة بـ PHP، وخدمة بـ Node.js، وخدمة بـ Go، بناءً على حالة الاستخدام؟

هذا هو جوهر الخدمات المصغرة، أن تكون غير معتمد على لغة معينة (Language-agnostic). لكن هذا يأتي مع عوائق سوقية، مثل توفر المطورين.

بالنسبة لي، لا أذهب إلى Go إلا إذا كانت الحالة تستدعي ذلك حقاً. على سبيل المثال، كان لدي خادم صغير يقبل طلباً من فريق العمليات لدينا، ويتحدث مع Kubernetes لإنشاء عميل جديد.

بكم من الموارد تم ذلك؟

4 ميغابايت.

4 ميغابايت؟

نعم، 4 ميغابايت. في هذه الأثناء، تطبيق Node.js كان يستهلك 250 ميغابايت عند بدء التشغيل. هل هذا يعني أن Node سيئة؟ لا، لكل منها حالة استخدامها. في هذه الحالة، Go موجهة أكثر لعمليات التطوير (DevOps)، ومعظم الأدوات مبنية بها. لذا، أثق في استخدام Go هنا.

مشكلتك مع حزم Node.js هي أنك تعتمد على ثقة الشخص الذي أنشأها في المجتمع.

بالضبط. أنت تسلم رقبتك له.

فكرة Go هي أن تكون بسيطة ومرنة، بحيث يمكنك تشغيل كل شيء بأقل قدر ممكن من الاعتماد على المكتبات الخارجية.

نعم، في المثال الذي ذكرته، لم أحتج لتنزيل أي حزمة تقريباً سوى حزمة Google Kubernetes.

إذاً، اليوز كيس هي التي تحركك، ويجب أن تكون حقيقية وليست مجرد رأي شخصي.

لا أذهب إلى Go لأنني أحبها. يحكمني وضع الشركة، المرشحون المتاحون، وعدم التحيز. لا تقل “سنحتاج هذا بعد 20 عاماً”، بل انظر إلى ما تحتاجه اليوم.

تحدي تعدد التقنيات وقابلية الصيانة

إذا قمت بمزج التقنيات، فإن قابلية الصيانة (Maintainability) ستكون سيئة.

بالطبع. لذلك، ليس من المنطقي أن تذهب إلى Go لمجرد أن حالة الاستخدام تبدو مناسبة، بينما يمكن تنفيذها بـ Node ولديك موارد لديها خبرة أكبر في Node.

Go هو الجوكر الذي أستخدمه عندما تفشل اللغات الأخرى.

إذا كانت هناك حاجة ماسة للتحكم في الذاكرة، أو التزامن، أو كفاءة عالية جداً، وفشلت Node في تحقيق ذلك، حينها أذهب إلى Go.

هل التحول من لغة إلى أخرى صعب؟

إذا كنت ستنتقل إلى Go من أجل التزامن، وأنت لا تفهم نموذج التزامن الخاص بها، فسوف تفشل. معظم المطورين لم يدرسوا أساسيات علوم الحاسوب بعمق. إدخالهم إلى مفاهيم مثل goroutines و channels وتجنب تسرب الذاكرة (Memory Leaks) سيستغرق وقتاً وسيسبب مشاكل.

أنت كمدير هندسي، قم بتدريبهم.

هذا قد يفشل بشكل أسوأ من الأول، لأنك تذهب إلى لغة من أجل ميزة لا تفهمها.

التحول بين العقليات: من PHP إلى Node.js

عندما انتقلت من PHP إلى Node.js، هل حدث تحول في عقليتك؟

نعم. عندما كنت أعمل بـ PHP، لم يكن مفهوم Callbacks شائعاً، لأنه لم تكن هناك حاجة لتشغيل أشياء في الخلفية. كل عملية تنتهي ثم تبدأ التالية.

في Laravel، دورة حياة الطلب (Request Lifecycle) تمر عبر النواة (Kernel)، ثم نقوم بتمهيد (Bootstrap) موفري الخدمات (Service Providers)، وتسجيل المسارات (Routes)، ثم تشغيل وحدة التحكم (Controller)، وإنشاء اتصال بقاعدة البيانات، وعندما ننتهي، نغلق كل شيء. مع كل طلب جديد، نكرر نفس العملية.

هذه الفكرة تغيرت قليلاً.

نعم، لكن لا تزال هناك قيود. في Nest.js، أنت لست “طازجاً” مع كل طلب. وقت التشغيل (Runtime) ينتظرك. إذا لم تفصل بين سياقات الطلبات المختلفة، فقد تتداخل وتسبب مشاكل.

هذا يعني أن هناك احتمالاً كبيراً لتسرب الذاكرة في JavaScript بسبب طبيعتها؟

نعم، هذه من أصعب المشاكل في JavaScript. لديك ذاكرة مشتركة. إذا لم تتعامل مع هذا بشكل صحيح، يمكن أن تدخل في اتصال مستخدم آخر. في PHP، هذا مستحيل بسبب طبيعة اللغة.

هذه نقطة مثيرة للاهتمام. في PHP، من الصعب حدوث تسرب للذاكرة، وفي Node.js، من السهل حدوثه.

نفس المشكلة موجودة في Go أيضاً.

الخلاصة: متى تختار كل تقنية؟

  1. PHP (Laravel): الخيار الأول لأغلب المشاريع، خاصة تلك التي تخدم طلبات قياسية (CRUD)، تقارير، إلخ. نظامها البيئي قوي جداً ويوفر إنتاجية عالية.
  2. Node.js (Nest.js): إذا واجهت قيوداً في PHP، مثل الحاجة إلى تطبيقات الوقت الفعلي (Real-time)، أو بنية قائمة على الأحداث، أو دردشة حية، فإن Node.js هو الخيار المنطقي التالي بسبب طبيعته غير الحاجزة (Non-blocking).
  3. Go: الملاذ الأخير. إذا كنت بحاجة إلى تزامن حقيقي، كفاءة قصوى في الذاكرة، تحكم دقيق في العمليات، وفشلت Node.js في ذلك، حينها فقط تذهب إلى Go، ولكن بعد فهم عميق لنموذج التزامن الخاص بها.

القرار النهائي لا يعتمد على حبك للغة، بل على حالة الاستخدام، الموارد المتاحة، ومتطلبات العمل.

التعامل مع الأخطاء (Error Handling)

لنتحدث عن معالجة الأخطاء.

في PHP و Node.js، نستخدم try-catch. في Go، لا يوجد مفهوم الاستثناءات (Exceptions) بنفس الطريقة.

في Go، لا يوجد Exceptions، بل هناك panic، وهو ليس للاستخدام الطبيعي.

نعم، panic يعني أن الخدمة ماتت. في Go، نحن لا نعمل بالاستثناءات. في البداية، كنت مستغرباً جداً. كيف تعيشون هكذا؟

اللغة بسيطة جداً، حوالي 26 كلمة مفتاحية فقط.

نعم، لا يوجد classes، بل structs و interfaces. لا يوجد try-catch. كل دالة تقريباً تعيد قيمة وخطأ.

value, err := someFunction()
if err != nil {
    // Handle error
}

في البداية، كنت أجد هذا مزعجاً جداً. كل خطوة فيها معالجة خطأ. لكن عندما أطلقنا تطبيقات في بيئة الإنتاج (Production) باستخدام Go، شعرت براحة نفسية.

لماذا؟

لأنه لا توجد مساحة للخطأ غير المتوقع. كل شيء تتم معالجته. عندما نظرنا إلى أدوات المراقبة مثل Sentry، لم نجد أخطاء غير متوقعة. كل شيء متوقع ومعالج.

لكن هذا يعتمد على أنك فهمت النموذج جيداً وقمت بمعالجة الأخطاء.

بالطبع. في JavaScript، كم شخصاً يضع try-catch حول JSON.parse()؟ الأغلبية لا تفعل. هذا قد يسبب خطأ غير متوقع. في Go، أنت مجبر على معالجة الخطأ.

المشكلة في try-catch هي أن لديك كتلة من الكود، وأي سطر فيها قد يرمي استثناءً. من منهم الذي فعلها؟

بالضبط. في Go، أعرف بالضبط ما الذي أتعامل معه وما الخطأ الذي أعيده.

إذاً، المشكلة في النهاية هي مشكلة هندسة برمجيات. يجب أن تفهم كيفية معالجة الأخطاء قبل أن تبدأ.

نعم، المهندس الذي يصمم فقط للسيناريو السعيد (Happy Scenario) هو أغبى مهندس. Go تجبرك على التفكير في الحالات الهامشية (Edge Cases).

البرمجة المتزامنة (Concurrency)

لنتحدث عن التزامن.

PHP تبكي في الزاوية هنا. يتبقى لدينا Node.js و Go.

التزامن في Node.js أساسي جداً.

هو عبارة عن Promise.all(). هذا ليس تزامناً حقيقياً. لا يوجد ضمان بأنه سيكون تزامناً حقيقياً على خيوط حقيقية. لا أحد في عالم Node.js يتحدث عن “حالة التسابق” (Race Condition).

ما هي حالة التسابق (Race Condition)؟ تحدث عندما يحاول خيطان أو أكثر الوصول إلى ذاكرة مشتركة وتعديلها في نفس الوقت، مما يؤدي إلى بيانات غير متسقة.

في Node.js، لا يوجد ما يسبب حالة تسابق لأنه يعمل على خيط واحد.

في Go، الوضع مختلف تماماً.

نموذج التزامن في Go معقد. لديك goroutines و channels و wait groups.

في Go، لديك متحكم (Scheduler) يوزع المهام على goroutines. إذا كانت goroutine تنتظر شيئاً، فإن المتحكم يضعها في حالة “ركن” (go park) ويوفر الموارد حتى تصبح جاهزة مرة أخرى. هذا غير موجود في Node.js.

التحدي في التزامن هو إدارة الوصول إلى الموارد المشتركة.

هنا يأتي دور “القسم الحرج” (Critical Section) و “الإقصاء المتبادل” (Mutual Exclusion أو Mutex).

Mutex: آلية لقفل (lock) مورد مشترك حتى ينتهي خيط من استخدامه، ثم فتحه (unlock) ليستخدمه خيط آخر. هذا يمنع حالة التسابق.

الخطأ الشائع هو وضع القفل والفتح في المكان الخاطئ، مما يسبب اختناقات غير ضرورية.

لماذا Go؟

لأنها تمنحك تحكماً سلساً في الذاكرة والتزامن، مع تبسيط التعقيدات الموجودة في لغات مثل C++ أو Rust. جافا لديها مشكلة JVM. راست معقدة في إدارة الذاكرة. Go أخذت السياسة السهلة.

خلاصة التزامن

إذا كنت ستعمل في التزامن، تعلم مفاهيمه جيداً قبل أن تبدأ، بغض النظر عن اللغة.

من هو محمد أسامة؟

كيف وصلت إلى هذه المعرفة العميقة؟

جاءت بالصدفة. في بداية حياتي، كنت أذاكر 12 ساعة في اليوم. بدأت في عمر 15 أو 16 سنة. دخولي للمجال كان من باب Script Kiddies وأمن المعلومات. تعلمت الكثير، لكنني لم أجد نفسي في الجانب الشرير.

انجذبت إلى الويب لأنه ديناميكي جداً. بدأت بـ HTML، CSS، PHP. في مرحلة تعلم البرمجة، كنت مدمناً على المذاكرة.

بعد ذلك، بدأت في شرح الدورات عبر الإنترنت.

نعم، على Udemy. عملت أربع دورات: Vue، أفضل ممارسات Laravel، أساسيات Laravel، وشبكة Tor. كان ذلك في 2016. لكنني وجدت أن المجهود كبير والعائد لم يكن كما توقعت، خاصة أن المجتمع كان لا يزال يبحث عن الأساسيات.

متى بدأت العمل في الشركات؟

بعد ثلاث أو أربع سنوات من ذلك، نزلت في أول شركة ومنذ ذلك الحين وأنا في منصب إداري (Managerial). بدأت كقائد فريق (Team Lead)، ثم مدير، وهكذا. كل هذا وأنا في منصب إداري.

الإدارة وهندسة البرمجيات

كيف تكون في المقابلات كشخص لديه آراء قوية؟

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

ماذا عن تغيير ثقافة العمل؟

لا يمكنك تغيير معتقدات الناس بفرض الأوامر. يجب أن تشركهم في التجربة ليشعروا بالقيمة. هناك ما يسمى “هرم التغيير” (Change Pyramid): المعتقدات (Beliefs) تكون القيم (Values)، والقيم تقود إلى الأفعال (Actions).

المدير الشاطر هو أقل مدير يعمل.

لأنه وضع المبادئ والعمليات الصحيحة. عندما أغيب، يجب أن يستمر العمل كما هو.

ماذا عن التحفيز؟

ليس كل الناس يتحفزون بالمال. يجب أن تفهم الناس الذين تتعامل معهم. هناك ما يسمى “هرم ماسلو للاحتياجات”، وأعلى الهرم هو تحقيق الذات (Self-actualization). في هذه المرحلة، المال ليس هو المحفز الأساسي.

كلمة أخيرة: الذكاء الاصطناعي ومستقبل المبرمجين

ما هو انطباعك عن تأثير الذكاء الاصطناعي على المبرمجين الجدد؟

نحن مقبلون على كارثة. المبرمجون الذين يفتقرون إلى الأساسيات سيواجهون صعوبة. الذكاء الاصطناعي سيقوم بالأعمال المتكررة، لكن من سيقوم بالتحقق (Validation) وحل المشاكل المعقدة التي يعجز عنها الذكاء الاصطناعي؟ هم الأشخاص الذين يفهمون الأساسيات.

الذكاء الاصطناعي يحل 30% من المشكلة. الـ 70% الباقية هي التحقق، وهذا يأتي من فهم عميق لهندسة البرمجيات.

شكراً جزيلاً محمد على هذا الحوار الثري.

شارك المقال

أحدث المقالات

CONNECTED
ONLINE: ...
SECURE
00:00:00