الانتقال من monolith إلى microservices: التاريخ والممارسة

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

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

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

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

محتوى

العمارة ومشاكل الحل القائم


في البداية ، بدت البنية على النحو التالي: واجهة المستخدم - تطبيق منفصل ، تمت كتابة الجزء الأحادي في Visual Basic 6 ، وكان تطبيق .NET عبارة عن مجموعة من الخدمات ذات الصلة التي تعمل مع قاعدة بيانات كبيرة إلى حد ما.

مساوئ الحل السابق

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

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

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

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

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

التوقعات من الخدمات المصغرة


إصدار المكونات عندما تكون جاهزة. إصدار المكونات لأنها جاهزة عن طريق تفكيك المحلول وفصل العمليات المختلفة.

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

عزل الخدمات في عمليات منفصلة. من الناحية المثالية، أود عزلها في حاويات، ولكن عددًا كبيرًا من الخدمات المكتوبة في إطار عمل .NET لا تعمل إلا تحت Windowsبدأت الخدمات القائمة على .NET Core بالظهور الآن، ولكن لا يزال عددها قليلاً.

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

استخدام التقنيات الجديدة. هذا يهم أي مبرمج.

مشاكل الانتقال


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

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

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

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

كيفية الانتقال من متراصة إلى خدمات مصغرة


تخصيص الخدمات المصغرة

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

ما هي الطرق التي نستخدمها لعزل الخدمات المصغرة؟

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

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

يعد تجميع المضيف سطرًا واحدًا فقط من التعليمات البرمجية في فئة البرنامج. أخفينا العمل مع Topshelf في فصل دراسي مساعد.

namespace RBA.Services.Accounts.Host
{
   internal class Program
   {
      private static void Main(string[] args)
      {
        HostRunner<Accounts>.Run("RBA.Services.Accounts.Host");

       }
    }
}

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

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

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

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

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

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

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

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

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

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

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

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

العمل مع قاعدة البيانات


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

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

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

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

لقد طبقنا طريقتين عالميتين لتقسيم قاعدة البيانات: تقسيم الجداول الموجودة وتقسيمها بالتجهيز.

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

هناك حاجة إلى فرع مع معالجة عندما يتغير نموذج العمل كثيرًا ، ولم تعد الجداول ترضينا على الإطلاق.

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

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

بعد ذلك ، هناك طريقتان ممكنتان.

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

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

كلا النهجين يعملان ، اختر اعتمادًا على الموقف.

بعد أن نتأكد من أن كل شيء يعمل ، يمكن تعطيل الجزء المترابط الذي يعمل مع هياكل قاعدة البيانات القديمة.

الانتقال من monolith إلى microservices: التاريخ والممارسة

الخطوة الأخيرة هي إزالة هياكل البيانات القديمة.

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

العمل مع شفرة المصدر


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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

كنا محظوظين ، فلدينا مكتبات بنية تحتية يمكن استخدامها بشكل منفصل.

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

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

لقد قمنا بصياغة عدة قواعد لعملية تقسيم الكود.

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

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

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

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

قضايا البنية التحتية


تتعلق معظم الجوانب السلبية للانتقال إلى الخدمات المصغرة بالبنية التحتية. ستحتاج إلى نشر آلي ، ستحتاج إلى مكتبات جديدة لتشغيل البنية التحتية.

التثبيت اليدوي في البيئات

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

الانتقال من monolith إلى microservices: التاريخ والممارسة

نستخدم Atlassian و Bitbucket لتخزين المصدر و Bamboo للبنيات. نحب كتابة نصوص بناء في Cake لأنها نفس C #. تأتي الحزم الجاهزة إلى Artifactory ، وتصل Ansible تلقائيًا إلى خوادم الاختبار ، وبعد ذلك يمكن اختبارها على الفور.

الانتقال من monolith إلى microservices: التاريخ والممارسة

تسجيل منفصل


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

الانتقال من monolith إلى microservices: التاريخ والممارسة

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

اختبار وتصحيح الخدمات ذات الصلة


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

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

لقد أضفنا عملية اختبار آلية باستخدام مكتبة Specflow الشائعة. يتم تشغيل الاختبارات تلقائيًا بواسطة NUnit بمجرد نشرها من Ansible. إذا كانت تغطية المهام تلقائية بالكامل ، فلا داعي للاختبار اليدوي. على الرغم من أن الاختبار اليدوي الإضافي لا يزال مطلوبًا في بعض الأحيان. لتحديد الاختبارات التي يجب إجراؤها لمشكلة معينة ، نستخدم العلامات في Jira.

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

ماذا حققنا؟


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

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

يمكننا التحكم في مخطط النشر. يمكنك فصل مجموعات الخدمات بشكل منفصل عن باقي الحل ، إذا لزم الأمر.

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

ملخص

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

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

    ملاحظة: قصة أكثر عاطفية (كما لو كنت أنت شخصيًا) - بواسطة صلة.
    هنا النسخة الكاملة من التقرير.

المصدر: www.habr.com

شراء استضافة موثوقة للمواقع مع حماية DDoS وخوادم VPS VDS 🔥 اشترِ استضافة مواقع ويب موثوقة مع حماية من هجمات DDoS، وخوادم VPS وVDS | ProHoster