دليل معمارية البرمجيات (Software Architecture)

دليلك الشامل لفهم هندسة ومعمارية البرمجيات (Software Architecture) ومثال عملي باستخدام Nodejs

دليل معمارية البرمجيات (Software Architecture)

السلام عليكم ورحمة الله، أسعد الله صباحكم والمساء أحبتي الكرام.

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

هذا المجال الذي وجدتُه محيرًا ومثيرًا للمخاوف عندما بدأت رحلتي في البرمجة. لذلك سأحاول تبسيطها إليك بمقدمة في غاية اليُسر، تسهّل لك الطريق لفهم معمارية البرمجيات.

سنتحدث عن ماهية المعمارية في عالم البرمجيات، وبعض المفاهيم الرئيسية الواجب فهمها، وبعض أنماط المعماريات الأكثر استخدامًا اليوم.

سأضع لكل موضوع مقدمة موجزة ونظرية. ثم أشاركك بعض الأمثلة للشيفرات البرمجية لتكون لديك فكرة أوضح عن كيفية عملها. هيا بنا وقل بسم الله.

جدول المحتويات

ماهية معمارية البرمجيات

وفقًا لهذا المصدر:

معمارية البرمجيات لنظام ما تمثّل قرارات التصميم المتعلقة بهيكل النظام كلّه وسلوكه.

هذا الكلام عام وفضفاض جدًا. أليس كذلك؟ بالطبع. وهذا ما حيّرني كثيرًا عند البحث عن معمارية البرمجيات. لأنه موضوع يعجّ بكثير من المصطلحات التي تتحدث عن كثيرٍ من الأشياء.

أمّا أسهل طريقة يمكنني أن أصفها هي أن معمارية البرمجيات تشير إلى كيفية تنظيم الأشياء أثناء بناء البرمجيات. وتشمل هذه "الأشياء" ما يلي:

  • تفاصيل التنفيذ (أي بُنية مجلد المستودع البرمجي).
  • قرارات تصميم التنفيذ: هل تستخدم جانب الخادوم (Server Side) أو جانب العميل (Client Side)؟ هل قواعد بيانات علائقية (Relational) أو غير علائقية (Non Relational)؟
  • التقنيات المختارة: هل تستخدم تقنية REST أو GraphQL لواجهتك البرمجية (API)؟ أتستخدم Python مع Django أم Node مع Express لواجهتك الخلفية (Backend)؟
  • قرارات تصميم النظام: مثل، هل النظام مكوّن من وحدة متجانسة (monolith) أو مقسّم إلى خدمات مصغّرة (microservices)؟
  • قرارات البُنية التحتية: هل تستضيف برمجياتك محليًا أو في خدمة سحابية؟

هناك الكثير من الخيارات والاحتمالات المختلفة، وما يعقّد هذا أكثر أنه ضمن هذه الأقسام الخمسة يوجد الكثير من الأنماط التي يمكن دمجها. ويعني هذا أن بإمكاني الحصول على واجهة برمجية ذات وحدة متجانسة (Monolith API) تستخدم REST أو GraphQL، أو تطبيق قائم على الخدمات المصغرة يستضاف محليًا أو على السحابة، وما إلى ذلك.

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

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

ما هو نموذج العميل-الخادوم (Client-Server)؟

هو نموذج ينظّم المهام أو أعباء العمل للتطبيق بين المورد أو مزود الخدمة (الخادوم) وبين الخدمة أو طالب المورد (العميل).

ببساطة، العميل (Client) هو التطبيق الذي يطلب نوعًا من المعلومات أو ينفّذ الإجراءات، والخادوم (Server) هو البرنامج الذي يرسل المعلومات أو ينفّذ الإجراءات وفقًا لما يفعله العميل.

عادةً ما يُمثّل العملاء من خلال تطبيقات الواجهة الأمامية (Frontend) التي تعمل إما على الويب أو تطبيقات الجوال، على الرغم من وجود منصات أخرى أيضًا ويمكن للتطبيقات الخلفية (Backend) أن تعمل كأنها عملاء أيضًا. عادة ما تكون الخواديم تطبيقات خلفية.

لتوضيح ذلك بمثال، تخيّل أنك تدخل شبكتك الاجتماعية المفضّلة. عندما تُدخِل عنوان URL على متصفحك وتنقر على زر الإدخال، فإن متصفحك يعمل بمثابة تطبيق العميل مُرسلًا طلبًا (Sending A Request) إلى خادوم الشبكة الاجتماعية، والذي يستجيب (Responds) عن طريق إرسال محتوى موقع الويب إليك.

تستخدم معظم التطبيقات في الوقت الحاضر نموذج الخادوم-العميل. أهم مفهوم يجب تذكره هو أن العملاء يطلبون الموارد أو الخدمات التي يؤديها الخادم.

مفهوم آخر مهم يجب معرفته هو أن العملاء والخواديم جزء من نفس النظام، ولكن كلًا منها عبارة عن تطبيق / برنامج قائم بذاته. بمعنى أنه يمكن تطويرها واستضافتها وتنفيذها تنفيذًا منفصلًا.

إذا لم تكن على اطلاع بالفرق بين الواجهة الأمامية (Frontend) والواجهة الخلفية (Backend)، فهذه مقال رائع يشرح ذلك. وإليك مقال آخر يوسّع مفهوم الخادوم-العميل.

ما الواجهات البرمجية (APIs)؟

ذكرنا توًا أنّ العملاء والخواديم هي كيانات تتواصل مع بعضها البعض لطلب (Request) الأشياء والاستجابة (Respond) للأشياء. يتواصل هذان الجزءان عادة من خلال واجهة برمجية (API).

الواجهة البرمجية ليست أكثر من مجموعة من القواعد التي تحدد كيف يمكن لتطبيق ما التواصل مع تطبيق آخر. فهي كالعَقْدِ بين الجزأين الذي يقول:

"إذا أرسلت"أ"، فسأرد دائمًا "ب". إذا أرسلت "ج"، فسأرد دومًا "د" وما إلى ذلك.

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

هناك طرق مختلفة لتنفيذ الواجهة البرمجية. الأكثر استخدامًا هي REST و SOAP و GraphQL.

أما ما يتعلق بكيفية تواصل الواجهات البرمجية، فغالبًا ما يُستخدم بروتوكول HTTP ويُتبادل المحتوى بتنسيق JSON أو XML. لكن البروتوكولات وتنسيقات المحتوى الأخرى ممكنة تمامًا.

إذا كنت ترغب في التوسّع في هذا الموضوع، فإليك مقال لطيف لتقرأه.

ما هي التراكبية (القابلية للتركيب)؟

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

للتراكبية المزايا التالية:

  • جيدة لتقسيم المِيزات والاهتمامات، مما يساعد في تصوّر المشروع، وفهمه، وتنظيمه.
  • يميل المشروع إلى أن يكون أسهل في الصيانة، وأقل عرضة للأخطاء والمشاكل عندما يكون منظمًا ومقسمًا تقسيمًا واضحًا.
  • إذا كان مشروعك مقسمًا إلى عدة أجزاء مختلفة، فيمكن العمل على كل منها وتعديلها تعديلًا منفصلًا ومستقلًا، وهذا غالبًا يكون مفيدًا للغاية.

أعلمُ أن هذا يبدو عامًا وهائمًا بعض الشيء، لكن التراكبية أو ممارسة تقسيم الأشياء هو جزء كبير جدًا ممّا تدور حوله معمارية البرمجيات. لذلك فقط احتفظ بهذا المفهوم في عقلك. سيتّضح أكثر مع استعراض بعض الأمثلة. ؛)

إن كنت ترغب في مزيد من المعلومات حول هذا الموضوع طالع المقالة التالية استخدام الوحدات في JavaScript.

كيف تبدو بنتيك التحتية لمشروعك؟

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

على نحو أقل تجريدا، سنستخدم تطبيقًا افتراضيًا نسميه "مرئيات". 🤔🤫🥸

تعليق جانبي: ضع في اعتبارك أن هذا المثال قد لا يكون هو الأكثر واقعية، وأنني سأفترض المواقف أو أعتسفها من أجل تقديم مفاهيم معينة.

الفكرة هنا هي مساعدتك على فهم مفاهيم معمارية البرمجيات الأساسية بالمثال وليس لإجراء تحليل واقعي.

معمارية موحّدة (Monolithic)

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

ستكون الخدمات الرئيسية المضمّنة في تطبيقنا هي الاستيثاق (Authentication)؛ حتى يتمكن الأشخاص من إنشاء حسابات وتسجيل الدخول وما إلى ذلك، والمدفوعات (Payments)؛ حتى يتمكن الأشخاص من الاشتراك والوصول إلى المحتوى. لأنك لم تعتقد أن هذا كله مجاني، أليس كذلك؟ 😑 والبث (Streaming) بالطبع؛ حتى يتمكن الناس من مشاهدة ما يدفعون مقابله.

قد يبدو المخطط الأولي لمعمارية البرمجيات كما يلي:

«معمارية موحّدة تقليدية»
«شكل معمارية موحّدة تقليدية»

على اليسار لدينا ثلاثة تطبيقات واجهات أمامية (Frontend) مختلفة بمثابة العملاء (Clients) في هذا النظام. يمكن تطوير هذه التطبيقات باستخدام React و React-native على سبيل المثال.

لدينا خادوم واحد سيتلقى الطلبات (Requests) من جميع التطبيقات الثلاثة للعميل، ويتواصل مع قاعدة البيانات عند الاحتياج، ويستجيب (Respond) لكل واجهة أمامية وفقًا لذلك. يمكن تطوير الواجهة الخلفية باستخدام Node و Express مثلًا.

يُطلق على هذا النوع من المعمارية اسم المعمارية الموحّدة (Monolith) نظرًا لوجود تطبيق خادوم واحد م عن جميع ميزات النظام. في حالتنا، إذا أراد المستخدم الاستيثاق (Authenticate) أو الدفع لنا أو مشاهدة أحد أفلامنا، فسُترسل جميع الطلبات (Requests) إلى تطبيق الخادوم نفسه.

الفائدة الرئيسية للتصميم الموحّد هي بساطته. يُعد تشغيله وإعداده أمرًا يسيرًا وسهل المتابعة، ولهذا السبب تبدأ معظم التطبيقات بهذه الطريقة.

معمارية الخدمات المصغّرة (Microservices)

اتضح أن "مرئيات" نجح نجاحًا باهرًا. أطلقنا للتو أحدث موسم من "الجزائر، جنة الله في أرضه"، وهو مسلسل علمي، وفيلمنا "Agent 404" يحطم كل الأرقام القياسية، وهو عن عميل سري يتسلّل إلى شركة مدعيًا أنه مبرمج خبير ولكنه في الواقع لا يعرف شيئًا عن البرمجة.

بتنا نحصل على عشرات الآلاف من المستخدمين الجدد كل شهر من جميع أنحاء العالم، وهو أمر رائع لعملنا التجاري، ولكن ليس كذلك لتطبيقنا ذي المعمارية الموحّدة (Monolithic).

لقد واجهنا مؤخرًا تأخيرات في أوقات استجابة الخادوم (Server Response Times)، وعلى الرغم من أننا وسّعنا نطاق الخادوم رأسيًا (Vertically Scaled)، حيث أضفنا المزيد من ذاكرة الوصول العشوائي (RAM) ووحدة معالجة الرسوميات (GPU). لا يبدو أن الخادوم المسكين قادر على تحمل العبء الذي يستهلكه.

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

عند تحليل هذه المشكلة بعمق، وجدنا أن الميزة التي تستهلك معظم الموارد هي البث (Streaming)، في حين أن الخدمات الأخرى مثل الاستيثاق (Authentication)  والمدفوعات لا تمثل عبئًا كبيرًا جدًا.

لحل هذه المشكلة، سننفّذ معمارية الخدمات المصغرة التي ستبدو كما يلي:

«شكل أول تطبيقنا للخدمات المصغرة»
«أول تطبيقنا للخدمات المصغرة»

لذا إذا كنت جديدًا على هذه المعمارية، فقد تفكر "ما كل هذا؟!". أليس كذلك؟ حسنًا، يمكننا تعريفها على أنها مفهوم تقسيم ميزات جانب الخادوم إلى العديد من الخواديم الصغيرة المسؤولة عن ميزة واحدة أو بضع ميزات محدّدة.

باتّباع مِثالِنا، سابقًا، كان لدينا خادوم واحد فقط مسؤول عن جميع الميزات كما في المعمارية الموحّدة (Monolithic).

بعد تنفيذ معمارية الخدمات المصغرة، سيكون لدينا خادم واحد مسئول عن الاستيثاق (Authentication)، وآخر عن المدفوعات، وآخر عن البث (Streaming)، وآخرها مسئول عن التوصيات.

ستتواصل التطبيقات في جانب العميل مع خادوم الاستيثاق عندما يريد المستخدم الدخول، وستتواصل مع خادوم المدفوعات عندما يريد المستخدم الدفع، ومع خادوم البث عندما يريد المستخدم مشاهدة شيء ما.

كل هذا التواصل  يتم عبر الواجهات البرمجية (API) كما يحدث في الخادوم الموحّد (Monolithic)، أو عبر أنظمة تواصل أخرى مثل Kafka أو RabbitMQ. الفرق الوحيد أن لديك عدة خواديم مسئولة عن أعمال مختلفة بدلًا عن خادوم واحد مسئول عن كل هذه الأعمال.

قد يبدو هذا معقّدًا نوعًا ما، ولكن معمارية الخدمات المصغّرة توفّر الفوائد التالية:

  • تستطيع توسيع خدمات معينة بحسب الحاجة، بدلًا من توسيع البرمجة الخلفية (Backend) مرة واحدة. وباتباع مثالنا، فإننا وسّعنا كل نطاق الخادوم رأسيًا، على الرغم من أن الميزة التي كانت تحتاج إلى المزيد من الموارد كانت خدمة البث فقط. الآن لدينا خدمة البث منفصلة في خادوم خاص بها، وبهذا يمكننا توسيعها فقط وإبقاء بقية الخدمات كما هي ما دامت تعمل كما يجب.
  • ستكون الخدمات مرنة الترابط (Loosely Coupled)، مما يعني أننا سنتمكّن من تطوير ونشر الخدمات نشرًا منفصًلا.
  • سيكون حجم الشيفرة البرمجية لكل خادوم أقل وأسهل. وهو أمر جيد للتقنيين الذين كانوا يعملون معنا منذ البداية، وكذلك أسهل وأسرع للمطورين الجدد للفهم.

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

إذا أردت معرفة المزيد عن الخدمات المصغرة، هذا شرح جيد جدًا عن الموضوع.

ما الواجهة الخلفية للواجهة الأمامية (Backend For Frontend)؟

إحدى المشاكل التي تأتي عند تنفيذ الخدمات المصغرة هي التواصل مع تطبيقات الواجهات الأمامية التي تزداد تعقيدًا. لدينا الآن العديد من الخواديم المسئولة عن العديد من الأمور، مما يعني أن تطبيقات الواجهات تحتاج إلى تتبّع هذه المعلومات لمعرفة إلى من تُرسل الطلبات.

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

فائدة هذا النمط أننا نستفيد من معمارية الخدمات المصغّرة، دون زيادة تعقيد التواصل مع تطبيقات الواجهات الأمامية.

«شكل تنفيذنا لنمط الواجهة الخلفية للواجهات الأمامية»
«تنفيذنا لنمط الواجهة الخلفية للواجهات الأمامية»

هذا فيديو يشرح نمط الواجهة الخلفية للواجهة الأمامية (Bff) إذا كنت تريد معرفة المزيد عنه.

كيفية استخدام موزّعات الحِمْل (Load Balancers) والتوسّع الأفقي (Horizontal Scaling)

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

مرة أخرى وجدنا أن خدمة البث هي الخدمة التي تحت الضغط، ووسَّعنا رأسيًا كل الخواديم بحسب استطاعتنا. زدنا على ذلك بزيادة تقسيم الخدمة إلى المزيد من الخدمات المصغرة، لكن بلا فائدة؛ لذلك قررنا توسيع الخدمة أفقيًا (Horizontally Scale).

قبل إشارتنا إلى أن التوسيع الرأسي يعني إضافة المزيد من الموارد مثل الذاكرة (RAM)، ومساحة القرص (Disk Space) وغيرها إلى الحاسوب أو الخادوم. أما التوسيع الأفقي في المقابل، فيعني إعداد المزيد من الخواديم لعمل نفس المهمة.

بدلًا من وجود خادوم واحد مسئول عن البث، لدينا الآن ثلاثة خواديم. ومن ثم فالطلبات المعالجة من العملاء ستُوزّع بين هؤلاء الخواديم الثلاثة بحيث تتعامل كلها مع حمولة مقبولة.

تُوزّع الطلبات عادة بشيء يُدعى موزّع الحِمْل (Load Balancer). تعمل موزّعات الحمل بمثابة وكلاء عكسيين (Reverse Proxies) لخواديمنا، معترضة طلبات العملاء قبل وصولها للخادوم، ومعيدة توجيه الطلب إلى الخادوم المناسب.

بينما يكون التواصل المعتاد بين الخادوم والعميل كما في الشكل التالي:

«شكل هذا ما كان لدينا سابقًا»
«هذا ما كان لدينا سابقًا»

باستخدام موزّع الحِمْل يمكننا توزيع طلبات المستخدم خلال عدة خواديم:

«شكل هذا ما لدينا الآن»
«هذا ما لدينا الآن»

يجب أن تعلم أن التوسع الأفقي ممكن أيضًا مع قواعد البيانات كما هو ممكن مع الخواديم. أحد الطرق لتنفيذ هذا التوسيع عن طريق نموذج النسخة طبق الأصل (Source-Replica)، حيث ستتلقى قاعدة بيانات معينة جميع استعلامات الكتابة وتنسخ جميع بياناتها إلى واحدة أو أكثر من قواعد بيانات النسخ المتماثلة (Replica DBs). ستتلقى قواعد بيانات النسخ المتماثلة وتستجيب لجميع استعلامات القراءة.

ميزات النسخ المتماثلة لقواعد البيانات:

  • أداء أعلى: هذا النموذج يحسّن الأداء ويسمح بمعالجة المزيد من الاستعلامات أو الطلبات بالتوازي.
  • الموثوقية والتوافر: إذا دُّمر أحد خواديم قاعدة البيانات أو تعذر الوصول إليه لأي سبب من الأسباب، فلا تزال البيانات محفوظة في قواعد البيانات الأخرى.

لذلك بعد تنفيذ موزّع الحِمْل، والتوسيع الأفقي، والنسخ المتماثلة لقاعدة البيانات، ستبدو معماريتنا كالشكل التالي:

«شكل معماريتنا ذات التوسع الأفقي»
«معماريتنا ذات التوسع الأفقي»

هذا فيديو رائع يشرح موزّعات الحِمْل إن كنت مهتمًا بمعرفة المزيد.

تعليق جانبي: عندما نتحدث عن الخدمات المصغرة، وموزّعات الحِمْل، والتوسّع، نحن نتحدث دائمًا عن تطبيقات الواجهات الخلفية (Backend). أما تطبيقات الواجهات الأمامية فتطويرها غالبًا بالمعمارية الموحّدة، ومع ذلك هناك شيء غريب ومثير للاهتمام يدعى أيضًا الواجهات الأمامية المصغّرة (Micro-Frontends). 🧐

أين تقبع بنيتك التحتيّة (infrastructure)

صار لديك الآن فكرة مبدئية عن كيفية تنظيم البنية التحتية للتطبيق. الشيء الثاني الذي عليك أن تفكّر به هو أين تضع كل ما سبق.

كما سنرى، هناك ثلاثة خيارات لتقرير أين وكيف نستضيف التطبيق: محليًا، أو عند مزودي الخواديم التقليدية، أو على خدمة سحابية.

الاستضافة المحلية (On premise hosting)

تعني الاستضافة المحلية أنك تملك العتاد (Hardware) الذي يشّغل تطبيقك. في الماضي كانت هذه أكثر الطرق انتشارًا لاستضافة التطبيقات. كانت الشركات تخصص غرفًا خاصة بالخواديم، وتخصص فرقًا تقنية لإعداد وصيانة هذا العتاد.

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

تخيّل أنك تريد توسيع أحد الخواديم رأسيًا. هذا سيعني شراء المزيد من القطع، وإعدادها، والإشراف عليها باستمرار، وإصلاح ما فسد، وإذا أردت لاحقًا تقليص (Scale Down) الخادوم، فلن تستطيع إرجاع العتاد بعد شرائه.🥲

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

«شكل كيف نتخيل غرفة الخادوم في Notflix»
«كيف نتخيل غرفة الخادوم في تطبيقنا مرئيات»
«شكل كيف هو واقع الأمر»
«كيف هو واقع الأمر»

في حالة وحيدة تصبح الاستضافة المحلية معقولة، عندما تتعامل مع معلومات حساسة أو حرجة. تخيل مثلًا: برمجيات تشغّل مصنعًا للطاقة، أو معلومات بنكية حساسة. الكثير من هذه المؤسسات تقرر أن تكون الاستضافة محلية للتحكم الكامل بالعتاد والبرمجيات.

مزودو الخواديم التقليديون (Traditional server providers)

هناك خيار أكثر إراحة لأكثر الشركات هو التعامل مع مزودي الخوادم التقليديين. هذه الشركات التي تملك خواديمها وتؤجرها للآخرين. أنت تقرر ما نوع العتاد الذي تريده لمشروعك وتدفع مقابله مبلغًا شهريًا (أو لمدة مختلفة بحسب الاختيارات).

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

والشيء الجميل الآخر أن التوسيع أو التقليص سهل وخالٍ من المخاطر. لو أردت المزيد من العتاد، تدفع مقابله. ولو لم ترده مجددًا، تتوقف عن الدفع.

مثال على مزود خدمة مشهور هو Hostinger.

الاستضافة السحابية (Hosting on the Cloud)

إن كنت مطلعًا على التقنية مؤخرًا فقد طرقت مسامعك كلمة "السحابة" أو "الحوسبة السحابية" أكثر من مرة. تبدو لأول وهلة شيء مجرد وساحر نوعًا ما. لكن في الحقيقة لا تعني أكثر من وجود مراكز بيانات هائلة مملوكة من شركات مثل Amazon وGoogle، وMicrosoft وأضرابها.

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

وهذا ما يمكن أن نطلق عليه بالحوسبة السحابية. باستخدام خدمات متعددة مثل Amazon web service والتي اختصارها (Aws)، وGoogle Cloud، وMicrosoft Azure، صرنا قادرين على استضافة تطبيقاتنا على مراكز بيانات هذه الشركات، وأمكننا الاستفادة من كل هذه القدرة الحاسوبية.

«شكل كيف تبدو الخدمات السحابية»
«كيف تبدو الخدمات السحابية»

عند التعرف على الخدمات السحابية، من المهم ملاحظة وجود طرق مختلفة لاستخدامها:

الاستخدام التقليدي (Traditional)

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

الاستخدام المرن (Elastic)

ثاني الطرق أن تستفيد من الحوسبة "المرنة" المقدمة من معظم المزودين. تعني "المرونة" أن القدرة العتادية لتطبيقك سترتفع أو تتقلص آليًا بحسب استخدام التطبيق.

مثلًا، يمكنك البدء بخادوم ذي 8 جيجا بت من الذاكرة العشوائية (RAM) و500 جيجا بت من مساحة القرص. لو استقبل الخادوم المزيد من الطلبات ولم تعد هذه القدرات كافية لأداء عال، فإن النظام سيوسّع العتاد رأسيًا وأفقيًا توسيعًا آليًا.

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

اللا خادوم (Serverless)

طريقة أخرى يمكنك استخدام الحوسبة السحابية بها هي بالمعمارية المسمّاة "اللا خادوم".

باتّباع هذا النمط، لن تكون بحاجة خادوم يستقبل كل الطلبات ويستجيب بها. بدلًا من ذلك، سيكون لديك دوال مفردة (Individual Functions) مربوطة بنقطة وصول كما في الواجهات البرمجية (API).

ستُنفّذ هذه الدوال في كل مرة تستقبل طلبًا وتعالج العمل المنوط بها سواء للاتصال بقاعدة البيانات، أو تنفيذ أوامر الإنشاء والقراءة والتعديل والحذف (وتختصر إلى CRUD)، أو تنفيذ أي شيء آخر يقوم به خادوم معتاد.

الجميل حقًا في معمارية اللا خادوم، أنك يمكن تجاهل كل ما يتعلق بصيانة الخادوم وتوسيعه. لديك فقط دوال تٌنفّذ عندما تحتاجها، وكل دالة تتوسع وتتقلص آليًا عند الحاجة.

وأنت أيها المستخدم، تدفع فقط لعدد المرات التي تُنفّذ، وكمية وقت المعالجة لكل تنفيذ.

إن أردت تعلّم المزيد ، هذا شرح لنمط اللا خادوم.

العديد من الخدمات الأخرى

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

وإلى جانب الخدمات المتعلقة بالخادوم، فمزودو الخدمات السحابية يوفرون الكثير من الحلول المختلفة كقواعد البيانات العلائقية (Relational) وغير العلائقية (Non-Relational)، وخدمات تخزين الملفات، وخدمات الذاكرة المخبأة (Caching)، وخدمات الاستيثاق (Authentication)، وخدمات تعلّم الآلة (Machine Learning)، وخدمات معالجة البيانات، وخدمات مراقبة وتحليل الأداء، والمزيد. وكل هذا مستضاف على الخدمة السحابة.

من خلال أدوات مثل Terraform أو Aws Cloud Formation، يمكننا كذلك إعداد بنيتنا التحتية بوصفها شيفرات برمجية. مما يعني أننا نستطيع كتابة شيفرات برمجية (Script) تّعد الخادوم، وقاعدة البيانات، وأي شيء آخر قد نحتاجه على الاستضافة السحابية في غضون دقائق.

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

وكما أشرنا سابقًا، أكثر مزودي خدمات الاستضافة السحابية هم: AWS و Google Cloud و Azure. مع ذلك توجد خيارات أخرى مثل Ibm و DigitalOcean و Oracle.

معظم هؤلاء المزودين يوفرون خدمات متشابهة، ولكنها قد تكون بأسماء مختلفة. مثلًا، دوال اللا خادوم تُسمى "Lambdas" في AWS وتسمى "cloud functions" في خدمة Google السحابية.

هياكل مجلّد مختلفة جدير أن تعرفها

نعم، لقد رأينا كيف تُشير المعمارية إلى تنظيم البنية التحتية والاستضافة. دعنا الآن نرى بعض الشيفرات البرمجية وكيف أن المعمارية قد تشير إلى هياكل المجلد وتراكبية الشيفرة البرمجية.

هيكلية كل شيء في مجلد واحد

لتوضيح أهمية هيكلية المجلد، لنبنِ مثالًا اعتباطيًا لواجهة برمجية API. سنحاكي قاعدة بيانات وهمية للأرانب 🐰🐰 وسننفذ عليها عمليات الإنشاء والقراءة والتعديل والحذف (Crud). سنبنيها باستخدام Node وExpress.

هذه محاولتنا الأولى، دون أي هيكلية على الإطلاق. سيحتوي مستودعنا البرمجي كلًا من مجلد node modules وملفات app.js و package-lock.json و package.json.

«شكل المجلدات دون أي هيكلية»
«المجلدات دون أي هيكلية»

في داخل ملف app.js سيكون لدينا خادومنا الصغير، ومحاكاة قاعدة البيانات، ونُقطتَيْ وصول (endpoints):

// App.js

const express = require("express");

const app = express();

const port = 7070;

// Mock DB

const db = [
	{ id: 1, name: "John" },

	{ id: 2, name: "Jane" },

	{ id: 3, name: "Joe" },

	{ id: 4, name: "Jack" },

	{ id: 5, name: "Jill" },

	{ id: 6, name: "Jak" },

	{ id: 7, name: "Jana" },

	{ id: 8, name: "Jan" },

	{ id: 9, name: "Jas" },

	{ id: 10, name: "Jasmine" },
];

/* Routes */

app.get("/rabbits", (Req, Res) => {
	res.json(db);
});

app.get("/rabbits/:idx", (Req, Res) => {
	res.json(db[req.params.idx]);
});

app.listen(port, () =>
	console.log(`⚡️[server]: Server is running at http://localhost:${port}`)
);

لو اختبرنا نقطتي الوصول سنرى أنها تعمل دون مشاكل:

http://localhost:7070/rabbits

# [

#   {

#     "id": 1,

#     "name": "John"

#   },

#   {

#     "id": 2,

#     "name": "Jane"

#   },

#   {

#     "id": 3,

#     "name": "Joe"

#   },

#   ....

# ]

###

http://localhost:7070/rabbits/1

# {

#   "id": 2,

#   "name": "Jane"

# }

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

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

باتباع أسلوب التراكبية، فالطريقة الأفضل هي بوجود عدة مجلدات وملفات لمختلف المسئوليات والعمليات التي تتطلب التنفيذ.

لتوضيح هذا أكثر، لِنُضف ميزات إضافية لواجهتنا البرمجية ولنرَ كيف يكون اتّباع أسلوب التراكبية بمساعدة معمارية الطبقات (layers architecture).

هيكلية طبقات المجلدات

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

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

سيكون لتطبيقنا خمس طبقات، وسترتب كالتالي:

«شكل طبقات التطبيق»
«طبقات التطبيق»
  • سيكون في طبقة التطبيق الإعداد الأساسي للخادوم والاتصال للموجّهات (Routers) التي هي في الطبقة الثانية.
  • سيكون في طبقة الموجّهات (Routes) تعريف لكل الموجّهات والاتصال مع المتحكمات (Controllers) التي هي في الطبقة التي تليها.
  • سيكون في طبقة المتحكمات المنطق البرمجي الذي نريده لكل نقطة وصول والاتصال بطبقة النموذج (Model) التي في الطبقة الموالية، كما بدا لك.
  • وفي طبقة النموذج المنطق البرمجي للتعامل مع قاعدة البيانات المحاكاة.
  • أخيرًا، طبقة الثبات (Persistence) التي ستكون فيها قاعدة البيانات.

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

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

هذا يعني أن الطلب يجب أن يمرّ في البداية على الطبقة الأول، ثم الثانية، ثم الثالثة، وهكذا. يُمنع أن يتجاوز الطلب أي طبقة لأن ذلك من شأنه أن يعبث بمنطق المعمارية وفوائد التنظيم والتراكبية التي يقدمها لنا.

«شكل طريقة أخرى لتصوير المعمارية»
«طريقة أخرى لتصوير المعمارية»

لنرَ بعض الشيفرات البرمجية. باستخدام معمارية الطبقات، سيكون تقسيم المجلد كالتالي:

«شكل المجلدات بتطبيق هيكلية الطبقات»
«المجلدات بتطبيق هيكلية الطبقات»
  • لدينا مجلد جديد يدعى db سيحتوي ملف قاعدة البيانات.
  • ولدينا مجلد آخر يدعى rabbits سيحتوي الموجهات (Routes)، والمتحكمات (Controllers)، والنماذج (Models) المتعلقة بهذا الكيان (Entity) الذي هو rabbits.
  • وملف app.js يعد خادومنا ويتصل بالموجهات (Routers).
// App.js

const express = require('express');

const rabbitRoutes = require('./rabbits/routes/rabbits.routes')

const app = express()

const port = 7070

/* Routes */

app.use('/rabbits', rabbitRoutes)

app.listen(port, () => console.log(`⚡️[server]: Server is running at http://localhost:${port}`))
  • ملف rabbits.routes.js سيحتوي على نقاط الوصول المتعلقة بهذا الكيان وربطها بالمتحكمات المتعلقة بها (الدوال التي نريد أن تنفّذ عندما يصل الطلب إلى نقطة الوصول).
// rabbits.routes.js

const express = require('express')

const bodyParser = require('body-parser')

const jsonParser = bodyParser.json()

const { listRabbits, getRabbit, editRabbit, addRabbit, deleteRabbit } = require('../controllers/rabbits.controllers')

const router = express.Router()

router.get('/', listRabbits)

router.get('/:id', getRabbit)

router.put('/:id', jsonParser, editRabbit)

router.post('/', jsonParser, addRabbit)

router.delete('/:id', deleteRabbit)

module.exports = router
  • ملف rabbits.controllers.js يحتوي المنطق البرمجي المتعلق بكل نقطة وصول. هنا سنبرمج ما تأخذه الدالة من مدخلات، وما المعالجة المطلوبة، وما المخرجات. 😉 علاوة على ذلك، يرتبط كل متحكم (Controller) بدالة النموذج (Model) المقابلة (والتي ستؤدي العمليات المتعلقة بقاعدة البيانات).
// rabbits.controllers.js

const { getAllItems, getItem, editItem, addItem, deleteItem } = require('../models/rabbits.models')

const listRabbits = (Req, Res) => {

    try {

        const resp = getAllItems()

        res.status(200).Send(Resp)

    } catch (Err) {

        res.status(500).Send(Err)

    }

}

const getRabbit = (Req, Res) => {

    try {

        const resp = getItem(Parseint(Req.Params.Id))

        res.status(200).Send(Resp)

    } catch (Err) {

        res.status(500).Send(Err)

    }

}

const editRabbit = (Req, Res) => {

    try {

        const resp = editItem(Req.Params.Id, Req.Body.Item)

        res.status(200).Send(Resp)

    } catch (Err) {

        res.status(500).Send(Err)

    }

}

const addRabbit = (Req, Res) => {

    try {

        console.log( req.body.item )

        const resp = addItem(Req.Body.Item)

        res.status(200).Send(Resp)

    } catch (Err) {

        res.status(500).Send(Err)

    }

}

const deleteRabbit = (Req, Res) => {

    try {

        const resp = deleteItem(Req.Params.Idx)

        res.status(200).Send(Resp)

    } catch (Err) {

        res.status(500).Send(Err)

    }

}

module.exports = { listRabbits, getRabbit, editRabbit, addRabbit, deleteRabbit }
  • ملف rabbits.models.js هو المكان الذي نحدد فيه الدوال التي ستنفذ إجراءات الإنشاء والقراءة والتعديل والحذف (Crud) على قاعدة البيانات. تمثل كل دالة نوعًا مختلفًا من الإجراءات (قراءة واحد، وقراءة الكل، والتحرير، والحذف، وما إلى ذلك). هذا الملف هو الذي يتصل بقاعدة بياناتنا.
// rabbits.models.js

const db = require('../../db/db')

const getAllItems = () => {

    try {

        return db

    } catch (Err) {

        console.error("getAllItems error", err)

    }

}

const getItem = id => {

    try {

        return db.filter(Item => Item.Id === Id)[0]

    } catch (Err) {

        console.error("getItem error", err)

    }

}

const editItem = (Id, Item) => {

    try {

        const index = db.findIndex(Item => Item.Id === Id)

        db[index] = item

        return db[index]

    } catch (Err) {

        console.error("editItem error", err)

    }

}

const addItem = item => {

    try {

        db.push(Item)

        return db

    } catch (Err) {

        console.error("addItem error", err)

    }

}

const deleteItem = id => {

    try {

        const index = db.findIndex(Item => Item.Id === Id)

        db.splice(Index, 1)

        return db

        return db

    } catch (Err) {

        console.error("deleteItem error", err)

    }

}

module.exports = { getAllItems, getItem, editItem, addItem, deleteItem }
  • أخيرًا ملف db.js يحتوي قاعدة البيانات المحاكاة. في المشاريع الحقيقية، هذا المكان التي يكون فيه الاتصال بقاعدة البيانات.
// db.js

const db = [

    { id: 1, name: 'John' },

    { id: 2, name: 'Jane' },

    { id: 3, name: 'Joe' },

    { id: 4, name: 'Jack' },

    { id: 5, name: 'Jill' },

    { id: 6, name: 'Jak' },

    { id: 7, name: 'Jana' },

    { id: 8, name: 'Jan' },

    { id: 9, name: 'Jas' },

    { id: 10, name: 'Jasmine' },

]

module.exports = db

كما نرى، هناك الكثير من المجلدات والملفات ضمن هذه المعمارية. ولكن نتيجة لذلك، فإن شيفرتنا البرمجية صارت أكثر تنظيماً بوضوح. كل شيء له مكانه الخاص، والتواصل بين الملفات المختلفة محدد بوضوح.

هذا النوع من التنظيم يسهل كثيرًا إضافة الميزات الجديدة، وتعديل الشيفرة البرمجية، وإصلاح العلل البرمجية.

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

أنا أيضًا مؤيد لامتلاك مجلد لكل من الكيانات الرئيسية في تطبيقك (الأرانب "rabbits" في حالتنا). هذا يسمح بفهم أكثر وضوحًا لما يتعلق به كل ملف.

لنقل أننا نريد الآن إضافة ميزات جديدة لإضافة/تعديل/حذف القطط والكلاب أيضًا. سننشئ مجلدات جديدة لكل منها، ولكل منها الموجهات والمتحكمات وملفات النماذج. الفكرة هي فصل الاهتمامات ووضع  كل شيء في مكانه المناسب.👌👌

هيكلية مجلد نموذج-طريقة عرض-مُتحكِّم (MVC)

معمارية MVC هي معمارية ترمز إلى Model View Controller والتي تعني: نموذج-طريقة-متحكّم. يمكننا القول أن معمارية MVC تشبه تبسيط معمارية الطبقات، مع دمج جانب الواجهة الأمامي (UI) للتطبيق أيضًا.

ضمن هذه المعمارية، سيكون لدينا ثلاث طبقات:

  • طبقة العرض المسئولة عن عرض واجهة المستخدم.
  • طبقة المتحكّم المسئولة عن تحديد الموجهات (Routes) والمنطق البرمجي لكل منها.
  • طبقة النموذج المسئولة عن التفاعل مع قاعدة البيانات.
«شكل معمارية نموذج-عرض-متحكّم»
«معمارية: نموذج-عرض-متحكّم»

كما في السابق، ستتفاعل كل طبقة فقط مع الطبقة التالية، لذلك لدينا تدفق اتصال محدد بوضوح.

«شكل طريقة أخرى لرؤية المعمارية»
«طريقة أخرى لرؤية المعمارية»

هناك العديد من أطر العمل (Frameworks) التي تتيح لك تنفيذ معمارية MVC خارج الإطار الضيق (مثل Django أو Ruby on Rails على سبيل المثال). لعمل هذا باستخدام Node و Express ستحتاج إلى محرك القوالب EJS.

إذا لم تكن معتادًا على محركات القوالب (Template Engines)، فهي مجرد طريقة لعرض Html بسهولة مع الاستفادة من الميزات البرمجية مثل المتغيرات (Variables) والحلقات التكرارية (Loops) والجمل الشرطية (Conditionals)، وما إلى ذلك (تشبه إلى حد بعيد ما سنفعله مع Jsx في React).

كما سنرى بعد قليل، سننشئ ملفات EJS لكل نوع من الصفحات التي نرغب في عرضها، ومن كل متحكّم سنعرض هذه الملفات استجابة لنا، ونمرر لها الاستجابة المناسبة ومتغيراتها.

سيكون هيكل المجلد كالتالي:

«شكل هيكل المجلدات وفق معمارية MVC»
«هيكل المجلدات وفق معمارية MVC»
  • لاحظ أننا تخلصنا من معظم المجلدات التي كانت لدينا من قبل، واحتفظنا بمجلدات db والمتحكمات controllers والنماذج models.
  • أضفنا مجلد views تتوافق مع كل الصفحات / الاستجابات التي نريد عرضها.
  • ملفا db.js و models.js بقيت كما هي.
  • ملف app.js صار كالتالي:
// App.js

const express = require("express");

var path = require('path');

const rabbitControllers = require("./rabbits/controllers/rabbits.controllers")

const app = express()

const port = 7070

// Ejs config

app.set("view engine", "ejs")

app.set('views', path.join(__dirname, './rabbits/views'))

/* Controllers */

app.use("/rabbits", rabbitControllers)

app.listen(port, () => console.log(`⚡️[server]: Server is running at http://localhost:${port}`))
  • ملف rabbits.controllers.js تغيّر لتحديد الموجّهات، وربط دالة النموذج (Model)، وتقديم ملف العرض المناسب لكل طلب. لاحظ أنه في دالة العرض نمرر استجابة الطلب معاملًا إلى طريقة العرض. 😉
// rabbits.controllers.js

const express = require('express')

const bodyParser = require('body-parser')

const jsonParser = bodyParser.json()

const { getAllItems, getItem, editItem, addItem, deleteItem } = require('../models/rabbits.models')

const router = express.Router()

router.get('/', (Req, Res) => {

    try {

        const resp = getAllItems()

        res.render('rabbits', { rabbits: resp })

    } catch (Err) {

        res.status(500).Send(Err)

    }

})

router.get('/:id', (Req, Res) => {

    try {

        const resp = getItem(Parseint(Req.Params.Id))

        res.render('rabbit', { rabbit: resp })

    } catch (Err) {

        res.status(500).Send(Err)

    }

})

router.put('/:id', jsonParser, (Req, Res) => {

    try {

        const resp = editItem(Req.Params.Id, Req.Body.Item)

        res.render('editRabbit', { rabbit: resp })

    } catch (Err) {

        res.status(500).Send(Err)

    }

})

router.post('/', jsonParser, (Req, Res) => {

    try {

        const resp = addItem(Req.Body.Item)

        res.render('addRabbit', { rabbits: resp })

    } catch (Err) {

        res.status(500).Send(Err)

    }

})

router.delete('/:id', (Req, Res) => {

    try {

        const resp = deleteItem(Req.Params.Idx)

        res.render('deleteRabbit', { rabbits: resp })

    } catch (Err) {

        res.status(500).Send(Err)

    }

})

module.exports = router
  • أخيرًا، في ملفات العرض سنأخذ المتغير الذي استقبلنا معاملًا، ونعرضه على هيئة HTML.
<!-- Rabbits view -->

<!DOCTYPE html>

<html lang="en">

    <body>

        <header>All rabbits</header>

        <main>

            <ul>

                <% rabbits.forEach(Function(Rabbit) { %>

                    <li>

                        Id: <%= rabbit.id %>

                        Name: <%= rabbit.name %>

                    </li>

                <% }) %>

            </ul>

        </main>

    </body>

</html>

<!-- Rabbit view -->

<!DOCTYPE html>

<html lang="en">

    <body>

        <header>Rabbit view</header>

        <main>

                <p>

                    Id: <%= rabbit.id %>

                    Name: <%= rabbit.name %>

                </p>

        </main>

    </body>

</html>

الآن يمكننا فتح متصفح الإنترنت والنقر على الرابط http://localhost:7070/rabbits  والحصول على النتيجة التالية:

نتيجة كل العناصر

أو الذهاب إلى الرابط http://localhost:7070/rabbits/2 والحصول على النتيجة:

نتيجة عنصر واحد

وهذه هي معمارية MVC!

خاتمة

آمل أن كل هذه الأمثلة قد ساعدتك في فهم ما نتحدث عنه عندما نذكر "معمارية البرمجيات" في عالم البرمجيات.

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

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

تعلمنا عن التوسّع الرأسي والأفقي، والتطبيقات الموحّدة (Monolithic)، والخدمات المصغرة (Microservices)، والحوسبة السحابية المرنة، والحوسبة السحابية بلا خادم... والكثير من الأشياء. ولكن هذا ليس سوى غيض من فيض! لذا استمر في التعلم وابحث بنفسك. 💪💪

كما الحال دائمًا، أتمنى أن تكون قد استمتعت بالدليل وتعلمت شيئًا جديدًا.

مع أطيب التحيات وأراكم في المقال التالي! ✌️

ترجمة -وبتصرف- للمقال The Software Architecture Handbook لصاحبه Germán Cocca بمساعدة الأخ الحبيب واثق شويطر جزاه الله خيرا.

لا تنسونا من صالح دعائكم.

دُمتم بود.