paint-brush
كيفية التعامل مع التعقيد عند تصميم أنظمة البرمجياتبواسطة@fairday
64,467 قراءة٪ s
64,467 قراءة٪ s

كيفية التعامل مع التعقيد عند تصميم أنظمة البرمجيات

بواسطة Aleksei23m2024/02/05
Read on Terminal Reader
Read this story w/o Javascript

طويل جدا؛ ليقرأ

التعقيد هو العدو! دعونا نتعلم كيفية التعامل معه!
featured image - كيفية التعامل مع التعقيد عند تصميم أنظمة البرمجيات
Aleksei HackerNoon profile picture

ما هو كل هذا؟

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


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


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

ما هو الاهتمام المتقاطع؟

إذا نظرنا إلى ويكيبيديا، سنجد التعريف التالي


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


إنه يصف الأمر بشكل كبير، ولكنني أريد توسيعه وتبسيطه قليلاً:

إن الاهتمام المتقاطع هو مفهوم أو مكون للنظام/المنظمة الذي يؤثر على العديد من الأجزاء الأخرى (أو 'يتقاطع').


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


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

تصنيف الجوانب

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


لذلك، سوف أقوم بتقسيم الجوانب إلى ماكرو وميكرو .


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


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


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


في هذه المقالة، سأركز بشكل أساسي على الجوانب الكلية.

الجوانب الكلية

الهيكل التنظيمي

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


أية منظمة تقوم بتصميم نظام (بالمعنى الواسع) سوف تنتج تصميمًا تكون بنيته نسخة من بنية الاتصالات الخاصة بالمنظمة.


لقد اعتقدت دائمًا أن هذا المفهوم عالمي جدًا ويمثل القاعدة الذهبية.


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


Fantasy about keeping in mind a lot of bounded contexts

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


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


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


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

كرة كبيرة من الطين

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


Entertaining illustration made by ChatGPT

لتجنب إنشاء مثل هذا النوع من النظام، نحتاج إلى اتباع قواعد وقيود محددة.

هندسة النظام

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


تتعلق هندسة البرمجيات بالقرارات والاختيارات التي تتخذها كل يوم والتي تؤثر على النظام المبني.


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


نمط هندسة البرمجيات هو مجموعة من المبادئ والأنماط التي تحدد كيفية بناء البرمجيات.


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


على سبيل المثال، مثل:

  1. الهندسة المعمارية المتجانسة

  2. التصميم الموجه نحو المجال

  3. يعتمد على المكونات

  4. الخدمات المصغرة

  5. الأنابيب والمرشحات

  6. مُحرك الأحداث

  7. النواة الدقيقة

  8. موجه نحو الخدمة


وهكذا دواليك…


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


ومع ذلك، فإن الأنظمة المعقدة غالبا ما تتطلب بنية معقدة وشاملة، وهو أمر لا مفر منه.


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

  1. يبدأ الأمر دائمًا تقريبًا بأسلوب الهندسة المعمارية المتجانسة لأنه يزيل معظم المشكلات التي تنشأ بسبب طبيعة الأنظمة الموزعة. ومن المنطقي أيضًا اتباع أسلوب الهندسة المتجانسة المعيارية للتركيز على بناء المكونات بحدود واضحة. قد يساعد تطبيق نهج قائم على المكونات في التواصل مع بعضها البعض باستخدام الأحداث، ولكن إجراء مكالمات مباشرة (المعروفة أيضًا باسم RPC) يبسط الأمور في البداية. ومع ذلك، من المهم تتبع التبعيات بين المكونات لأنه إذا كان المكون A يعرف الكثير عن المكون B، فربما يكون من المنطقي دمجهما في واحد.
  2. عندما تقترب من الموقف الذي تحتاج فيه إلى توسيع نطاق تطويرك ونظامك، يمكنك التفكير في اتباع نمط Stangler لاستخراج المكونات التي تحتاج إلى نشرها بشكل مستقل أو حتى توسيع نطاقها وفقًا لمتطلبات محددة تدريجيًا.
  3. الآن، إذا كانت لديك رؤية واضحة للمستقبل، وهو ما يمثل حظًا لا يصدق، فيمكنك أن تقرر البنية المطلوبة. في هذه اللحظة، يمكنك أن تقرر الانتقال نحو بنية الخدمات المصغرة من خلال تطبيق أساليب التنسيق والتنظيم، ودمج نمط CQRS لعمليات الكتابة والقراءة على نطاق مستقل، أو حتى أن تقرر الالتزام بالبنية المتجانسة إذا كانت تناسب احتياجاتك.


من المهم أيضًا فهم الأرقام والمقاييس مثل DAU (المستخدمون النشطون يوميًا)، وMAU (المستخدمون النشطون شهريًا)، وRPC (الطلب في الثانية)، و TPC (المعاملة في الثانية)، حيث يمكن أن يساعدك ذلك في اتخاذ الخيارات لأن الهندسة المعمارية لـ 100 مستخدم نشط و100 مليون مستخدم نشط مختلفة.


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

اختيار مجموعة التكنولوجيا

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


هذه هي قائمة الاعتبارات الأساسية لاختيار مجموعة التكنولوجيا:

  • متطلبات المشروع وتعقيده. على سبيل المثال، يمكن إنشاء تطبيق ويب بسيط باستخدام إطار عمل Blazor إذا كان لدى مطوري البرامج لديك خبرة في التعامل معه، ولكن نظرًا لعدم نضج WebAssembly، فإن اختيار React وTypescript لتحقيق النجاح على المدى الطويل قد يكون قرارًا أفضل
  • متطلبات قابلية التوسع والأداء. إذا كنت تتوقع تلقي كمية كبيرة من حركة المرور، فقد يكون اختيار ASP.NET Core بدلاً من Django خيارًا حكيمًا نظرًا لأدائه المتفوق في التعامل مع الطلبات المتزامنة. ومع ذلك، يعتمد هذا القرار على حجم حركة المرور التي تتوقعها. إذا كنت بحاجة إلى إدارة مليارات الطلبات المحتملة مع زمن انتقال منخفض، فقد يكون وجود Garbage Collection تحديًا.
  • التوظيف ووقت التطوير والتكلفة. في أغلب الحالات، هذه هي العوامل التي يجب أن نهتم بها. الوقت اللازم لطرح المنتج في السوق وتكلفة الصيانة واستقرار التوظيف هي العوامل التي تدفع احتياجات عملك دون عوائق.
  • خبرة الفريق والموارد. تعتبر مجموعة المهارات التي يتمتع بها فريق التطوير الخاص بك عاملاً بالغ الأهمية. وعادة ما يكون من الأكثر فعالية استخدام التقنيات التي اعتاد عليها فريقك بالفعل ما لم يكن هناك سبب قوي للاستثمار في تعلم مجموعة جديدة من التقنيات.
  • النضج. يمكن للمجتمع القوي والنظام البيئي الغني بالمكتبات والأدوات أن يسهلا عملية التطوير بشكل كبير. غالبًا ما تحظى التقنيات الشائعة بدعم مجتمعي أفضل، وهو ما قد يكون ذا قيمة لا تقدر بثمن لحل المشكلات وإيجاد الموارد. وبالتالي، يمكنك توفير الموارد والتركيز بشكل أساسي على المنتج.
  • الصيانة والدعم على المدى الطويل. ضع في اعتبارك مدى صلاحية التكنولوجيا على المدى الطويل. فالتكنولوجيات التي يتم تبنيها ودعمها على نطاق واسع أقل عرضة للتقادم وتتلقى عمومًا تحديثات وتحسينات منتظمة.


كيف يمكن أن يؤثر وجود مجموعات متعددة من التكنولوجيا على نمو الأعمال؟

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


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

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


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


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

نقطة الفشل الفردية (SPOF)

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


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

  1. التكرار. قم بتنفيذ التكرار للمكونات الحرجة. وهذا يعني وجود مكونات احتياطية يمكنها أن تتولى المهمة في حالة فشل المكون الأساسي. يمكن تطبيق التكرار عبر طبقات مختلفة من النظام، بما في ذلك الأجهزة (الخوادم والأقراص) والشبكات (الروابط والمفاتيح) والبرامج (قواعد البيانات وخوادم التطبيقات). إذا كنت تستضيف كل شيء في مزود سحابي واحد وحتى لديك نسخ احتياطية هناك، ففكر في إنشاء نسخة احتياطية إضافية منتظمة في مزود آخر لتقليل التكلفة الضائعة في حالة الكارثة.
  2. مراكز البيانات. قم بتوزيع نظامك عبر مواقع مادية متعددة، مثل مراكز البيانات أو مناطق السحابة. يعمل هذا النهج على حماية نظامك من الأعطال الخاصة بالموقع مثل انقطاع التيار الكهربائي أو الكوارث الطبيعية.
  3. التعافي من الفشل. قم بتطبيق نهج التعافي من الفشل لجميع مكوناتك (DNS وCDN وموازنات التحميل وKubernetes وبوابات API وقواعد البيانات). نظرًا لأن المشكلات قد تنشأ بشكل غير متوقع، فمن الأهمية بمكان أن يكون لديك خطة احتياطية لاستبدال أي مكون بنسخته المستنسخة بسرعة عند الحاجة.
  4. خدمات ذات توافر عالٍ. تأكد من تصميم خدماتك بحيث تكون قابلة للتوسع أفقيًا ومتاحة بدرجة عالية منذ البداية من خلال الالتزام بالمبادئ التالية:
    • مارس عدم وجود حالة خدمة وتجنب تخزين جلسات المستخدم في ذاكرة التخزين المؤقت. بدلاً من ذلك، استخدم نظام ذاكرة التخزين المؤقت الموزعة، مثل Redis.
    • تجنب الاعتماد على الترتيب الزمني لاستهلاك الرسائل عند تطوير المنطق.
    • قلل من التغييرات الجذرية لتجنب إزعاج مستهلكي واجهة برمجة التطبيقات. اختر التغييرات المتوافقة مع الإصدارات السابقة قدر الإمكان. ضع أيضًا في اعتبارك التكلفة، حيث قد يكون تنفيذ تغيير جذري أكثر فعالية من حيث التكلفة في بعض الأحيان.
    • دمج تنفيذ الهجرة في خط أنابيب النشر.
    • وضع استراتيجية للتعامل مع الطلبات المتزامنة.
    • تنفيذ اكتشاف الخدمة ومراقبتها وتسجيلها لتعزيز الموثوقية والقدرة على المراقبة.
    • تطوير منطق الأعمال ليصبح أيديولوجيا، مع الأخذ في الاعتبار أن فشل الشبكة أمر لا مفر منه.
  5. مراجعة التبعيات. قم بمراجعة التبعيات الخارجية بشكل منتظم وقلل منها. يمكن أن يؤدي كل اعتماد خارجي إلى حدوث مشكلات محتملة في SPOF، لذا من الضروري فهم هذه المخاطر والتخفيف منها.
  6. تبادل المعرفة بشكل منتظم. لا تنسَ أبدًا أهمية نشر المعرفة داخل مؤسستك. قد يكون الأشخاص غير متوقعين، والاعتماد على فرد واحد أمر محفوف بالمخاطر. شجع أعضاء الفريق على رقمنة معرفتهم من خلال التوثيق. ومع ذلك، كن حذرًا من الإفراط في التوثيق. استخدم أدوات الذكاء الاصطناعي المختلفة لتبسيط هذه العملية.

خاتمة

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


شكرا لكم على القراءة! نراكم في المرة القادمة!