انتشرت في الآونة الأخيرة مفهوم ال Microservices بشكل كبير، عشرات المقالات والدروس التي تتحدث عنها، العديد من المحاضرات والمؤتمرات حولها وفوائدها وعيوبها. وانقسم مجتمع المطورين لفريقين، فريق يرى أنها ليست شيئاً جديداً وأنها مجرد إعادة تسمية Rebranding لمفهوم ال SOA. وفريق يرى عكس ذلك. وبالرغم من كل تلك الأراء المتفاوتة، فال Microservices لها فوائد كبيرة خصوصاً في ال Agile Development (كل ما تود معرفته عن ال Agile) وتطوير التطبيقات المعقدة Complex Enterprise Applications.
في هذه السلسلة سوف نتحدث عن عدة مواضيع في ال Microservices، سواءً مرحلة تصميمها Designing، بنائها Building، ونشرها Deploying. سوف نتعلم هذه المعمارية ونقارنها بالمعمارية القديمة Monolith Architecture Pattern. وسنتحدث أيضاً عن فوائد وعيوب ال Microservices، وهل ستكون مناسبة لمشروعك أم لا، وكيف يمكنك تطبيقها.
محتويات السلسلة:
- مقدمة حول ال Microservices (المقالة الحالية)
- استخدام ال API Gateway في بناء Microservices
- التواصل Communications بين الخدمات المصغرة
سنبدأ أول فقرة ولنلقى الضوء على ال Microservices فوق 30 ألف قدم، وقبل المضي قدماً في حال قرأت هذه المرادفات “تطبيق، نظام، برنامج” فغالباً نعني نفس الشيء، وفي حالة كنا نتحدث عن شيء معين سوف نوضحه حينها.
بناء المشروع في شكل برنامج واحد Building Monolithic Application
لنفترض أنك تريد بناء تطبيق توصيل تاكسي Taxi Application وتريد منافسة Uber وغيرها من الشركات، بعد عدة اجتماعات Meetings وجلب للمتطلبات Requirement Gathering قمت بالبدء في تطوير النظام وكتابة الأجزاء الداخلية فيه.
كطبيعة هذا النوع من المشاريع، فهي تحتوي على عدة أمور:
- واجهات ربط APIs لكي تتواصل مع التطبيقات (تطبيق العميل، وتطبيق السائق)
- واجهات رسومية Web UI لمدراء النظام لمشاهدة التقارير والإحصاءات وسير العمليات
- الدفع الالكتروني واستخدام أي خدمة خارجية لإتمام الدفع الالكتروني
- التنبيهات واستخدام أي خدمة خارجية لإرسال التنبيهات Emails, SMS
- خصائص النظام الأساسية: الرحلات Trips، السائقين Drivers، العملاء Passengers، الفواتير Billings والطلبات Orders.
- قاعدة البيانات: لتخزين كل هذه البيانات
غالباً في النسخة الأولى سوف تكون كل هذه الأمور في نفس النظام كما في الشكل التالي:
داخلياً إذا كان المطور يتبع طريقة سليمة في الكتابة وتقسيم الدوال والبناء فسوف يكون Modular Architecture (مثلاً يستخدم مفاهيم التصميم الكائني SOLID، عمل التسمية بشكل جيد الخ) ويمكن ألا يكون كذلك أيضاً.
الآن بعد تجهيزه للنشر Packaging سوف ينشر ككتلة واحدة Deployed as Monolith وقد يختلف ال Format بحسب اللغة وال Framework المستخدم، مثلاً في تطبيقات الجافا فهي تكون WAR Files وتنشر على مخدم الويب مثلاً Tomcat، وبعض مشاريع الجافا تكون ايضاً JAR. مثلاً مشاريع ال Rails وال Node.js تكون على شكل مجلد من الملفات.
كتابة المشاريع بهذه الطريقة Monolithic Apps منتشرة جداً، فهي:
- سهلة للتطوير حيث أغلب أدوات التطوير IDEs تركز على واحد بناء نظام واحد
- سهلة الاختبار فيمكن عمل end-to-end testing عن تطريق تشغيل النظام واختبار الواجهات باستخدام Selenium.
- سهلة النشر فبمجرد عمل ال Packaging تستطيع نشره في السيرفر،
- سهلة التوسعة Scaling بواسطة تشغيل عدة نسخ من هذا المشروع خلف Load Balancer.
اذاً في المراحل الأولية للمشروع Early Stage، هذه المعمارية تعمل بشكل جيد.
الزحف الى جحيم ال Monolithic
لسوء الحظ فهذه الطريقة لديها حدود كثيرة Limitations، فالتطبيقات الناجحة تنمو بشكل سريع مع الوقت وتصبح ضخمة في مرحلة ما، ففي كل دورة تطوير Sprint/Iteration يقوم المطورين بتطوير عدة خصائص Stories والتي بكل تأكيد تضيف المزيد من الأسطر في المشروع. بعد عدة أشهر أو سنوات سوف يكون مشروع ضخم للغاية monstrous monolith.
عندما يصبح المشروع معقداً Complex Monolith فسوف يبدأ فريق التطوير بالشعور بالألم، فأي محاولات للتطوير السريع Agile Development أو النشر السريع سوف تفشل. أحد المشكلات الرئيسية أن المشروع أصبح معقداً للغاية وهو أكبر من أن يفهمه مطور واحد بشكل جيد. ونتيجة لذلك، اصلاح المشاكل، تطوير الخصائص الجديدة سوف تصبح صعبة وتأخذ وقتاً كبيراً. إضافة الى ذلك سوف تأتي مرحلة الهبوط Downwards Spiral حيث أن المشروع صعب الفهم، فأي تعديلات لا تتم بشكل سليم سوف تأثر على المشروع من ناحية المشاكل Bugs وغيرها، سوف ينتهي الأمر بك مع كرة وحشية كبيرة من الطين big ball of mud.
الحجم الكبير للمشروع سوف يؤدي الى ابطاء عملية التطوير Slow Down Development، فكلما أصبح المشروع أكبر، كلما زاد وقت التشغيل Startup Time، في أحد الاستبيانات القديمة وجد بعض المطورين أن وقت التشغيل لديهم قرابة 12 دقيقة، وبعض الحالات الشاذة وصلت الى 40 دقيقة فقط لتشغيل المشروع. المطورين عموماً يقوموا بإعادة التشغيل بشكل مكثف اثناء التطوير، فاذا كان جزء كبير من يومهم هو الانتظار فهذا بكل تأكيد سوف يؤثر على الإنتاجية Productivity.
مشكلة أخرى في هذه التطبيقات الكبيرة المقعدة Complex Monolith App وهي أنها عقبة لل Continuous Deployment، فاليوم من الطبيعي لمشاريع SaaS أن يتم نشر التحديثات فيها بشكل يومي لل Production. وهذا الأمر صعب للغاية مع المشاريع ال Monolith لأنك تحتاج لإعادة نشر المشروع بالكامل ولو لتغيير جزء بسيط منه. ضع في اعتبارك مشكلة وقت التشغيل حينها لحظة النشر، وأيضاً الاختبار اليدوي المكثف للمشروع. لذلك ال CI/CD هي أقرب للمستحيل في هذه المشاريع.
أيضاً ال Monolith Applications لتحمل الضغط Scaling من خلال ال Load Balancers، لا يأخذ في الاعتبار مسألة أن هناك أجزاء في المشروع قد تكون لديها متطلبات مختلفة عن الأجزاء الأخرى Conflicting Resources Requirements، مثلاً أحد الجزئيات Module تقوم بعمل عمليات CPU-Intensive لمعالجة الصور وكان من الأفضل نشرها على سيرفرات لديها Computing عالية لها. وجزئية أخرى Module تحتوي على قاعدة بيانات في الذاكرة in-memory database فأيضا لهذه الجزئية الأفضل أن تعمل على سيرفر لها ذاكرة عالية. لكن بسبب أن كل الجزئيات تنشر ككتلة واحدة فسوف تقع في هذه المشكلة، وتضطر إما لدفع تكاليف عالية لكي تكون كل السيرفرات بنفس المواصفات، او التنازل عن الأداء الأفضل.
مشكلة أخرى في هذه المشاريع وهي Reliability، فبسبب أن كل الجزئيات تعمل في نفس البرنامج Process، فأي مشكلة في أي جزئية مثلاً Memory Leak سوف تؤثر على كل ال Process وبالتالي قد تتعطل ويوقف المشروع بالكامل. وحتى لو كان هناك Load Balancer خلف أكثر من Instance، فلأنها كلها نفس النسخة من هذا البرنامج فهذه ال Bug ستتواجد بهم كلهم وبالتالي ستقف كل ال Instances من العمل.
أخيراً وليس أخراً، هذه المشاريع صعبة جداً لكي تتغير وتعتمد على لغة او إطار عمل جديد Difficult To Adaptation، مثلاً لدينا مشروع فيه 2 مليون سطر باستخدام إطار عمل XYZ، فسوف يكون مكلفاً للغاية من ناحية الوقت والجهد إعادة كتابة كل البرنامج باستخدام إطار عمل جديد ABC وحتى إذا كان أفضل من كل النواحي. كنتيجة لذلك سوف يكون هناك عائق أمام تبني التقنيات الجديد وستكون عالقاً مع التقنية التي اخترتها عند بداية المشروع.
كملخص: لديك تطبيق مهم وناجح Successful Business-Critical Application نما بشكل كبير الى معمارية ضخمة قبيحة، ولا يفهمها الا عدد قليل جداً من المطورين (إذا وجد مطورين يستطيعوا فهمها). وتم كتابتها باستخدام تقنية قديمة Obsolete، غير منتجة Unproductive سوف تجعل عملية توظيف المطورين الموهوبين Talents صعبة ايضاً، والتطبيق يصعب توسيعه Scaling، وغير موثوق به Unreliable، والنتيجة أن ال Agile Development and Delivery هي مستحيلة!
اذاً، ماذا تستطيع أن تفعل حيال هذه المشاكل؟
الخدمات المصغرة Microservices – معالجة التعقيدات Tackling Complexity
الكثير من الشركات مثل Amazon, eBay, Netflix حلت هذه المشاكل بتبني ما يعرف الآن ب Microservices Architecture Pattern، فبدلاً من بناء المشروع كبرنامج واحد Monolith Application، فسيتم تقسيم المشروع الى عدة جزئيات صغيرة Interconnected Services.
الخدمة Service هي مجموعة من الخصائص المهام المتعلقة ببعضها، مثلاً إدارة الطلبات Order Management، إدارة العملاء Customer Management، وهكذا. وكل خدمة مصغرة Microservices هي تعتبر مشروع كامل صغير ولديها داخلياً معمارية لها. بعض من هذه الخدمات المصغرة Microservices يمكن أن تقدم Expose API سوءاً للتطبيقات أو لخدمات مصغرة أخرى. وبعض هذه ال Microservices قد تكون هي Web UI. وفي وقت التشغيل كل من هذه الخدمات سوف تكون على VM لوحدها او Docker Container. الصورة التالية تبين شكل المشروع بعد تبني هذه الطريقة في العمل:
كل المهام الوظيفية المتعلقة ببعضها سوف تجد أنها بداخل أحد هذه الخدمات المصغرة، حتى ال Web App هناك جزئين، الأولى هي المتعلقة بالركاب، والثانية بالسائقين. هذا يجعل من الأسهل نشر خصائص جديدة مثلاً للسائقين بدلاً من نشر المشروع ككل كما كان في الطريقة السابقة Monolith.
بعض هذه الخدمات تقدم REST API وبعضها يقوم بالاستفادة Consume من ال APIs، مثلاً ال Driver Management يستخدم خدمة Notification لكي خبر السائق المتواجد عن الرحلة المحتملة. مثلاً UI service قد تُستخدم بواسطة خدمات أخرى لعرض صفحة ويب وهكذا. وهذه الخدمات قد تكون غير متزامنة asynchronous وmessage-based communication، سوف نتحدث عن التواصل بين الخدمات inter-service communication في مقالة منفصلة لاحقاً.
وبعض هذه الخدمات تقدم REST API يتم استخدامها بواسطة التطبيقات Mobile Apps مثلاً لتطبيق السائق او الراكب. والتطبيق لا يستطيع الوصول لكل الخدمات Microservices وانما تكون هناك طبقة اتصال وسيطة تعرف بال API Getaway، وال API Getaway هي المسؤولة عن المهام مثلاً Load Balancing والحفظ المؤقت Caching، وحماية الوصول Access Control، ومراقبة الاتصالات Monitoring، وايضاً وضع القيود على استخدام الدوال API Metering. سوف نتحدث ايضاً عن ال API Getaway لاحقاً في مقالة منفصلة.
في كتاب ال Art of Scalability قام المؤلف بطرح مكعب ثلاثي الأبعاد Scale Cube وهو نموذج لتمثيل فكرة ال Scalability (ما هي ال Scalability وكيف يتحمل الموقع ملايين الزوار؟) وسوف تلاحظ المكعب في الصورة بالأسفل، حيث ذكر أن هناك 3 أبعاد ال Scaling:
- البعد الأول X-axis وهو عن طريق نشر عدة نسخ من نفس البرنامج خلف Load Balancer.
- البعد الثاني Y-axis عن طريق تقسيم المهام الى خدمات مصغرة باستخدام Microservices.
- البعد الثالث Z-axis عن طريق تقسيم البيانات، وهو هجين بين النوع الأول والثاني، حيث يتم نشر نفس النسخة في عدة سيرفرات، ولكن لكل منها مجموعة من البيانات، ويكون هناك جزء في المشروع يحدد أي يذهب الطلب الى أي سيرفر تتواجد فيه البيانات وذلك بالاستفادة من بيانات الطلب مثلاً المفتاح Primary Key أو رقم العميل.
التطبيقات قد تستخدم هذه الابعاد الثلاثة معاً، فيستخدم ال Y-axis ويتم عمل ال scaling عن طريق تقسيم المشروع الى Microservices، وفي وقت التشغيل runtime يتم عمل X-axis عن طريق نشر كل خدمة أكثر من مرة خلف Load Balancer وذلك لمزيد من ال Throughput and Availability، وأيضاً قد تستخدم بعض التطبيقات Z-axis في بعض الحالات (لل Database Scaling).
الصورة التالية توضح Trip Management Service والتي تم نشرها خلف Docker في Amazon EC2 وبالتالي تم تطبيق Y-axis وال X-axis.
في وقت التشغيل فال Trip Management Service تم نشرها على عدة Instances، وكل منها بداخل Docker Container (ما هي تقنية Docker؟ دليلك الكامل لتعلم Docker) . وللحصول على تواجديه أعلى فال Containers تعمل على عدة Cloud VMs، وفي الواجهة هناك Load Balancer يقوم بتوزيع الطلبات على هذه ال Instances. ويؤدي المهام الأخرى كالحفظ المؤقت، وحماية الوصول وغيرها.
استخدام ال Microservices يؤثر بشكل كبير على طريقة العمل مع قواعد البيانات، بدلاً من أن تكون هناك قاعدة بيانات واحدة تشترك بها كل الخدمات، فهنا سوف تكون لكل خدمة قاعدة البيانات الخاصة بها. هذه الفكرة تبدوا غريبة خصوصاً لمن تعود على one data model، وايضاً قد تسمح بتكرار بعض البيانات في الخدمات المختلفة. لكن وجود قاعدة بيانات لكل خدمة هو شرط أساسي إذا كنت تريد الحصول على فوائد ال Microservices والتي من أهمها تقليل الترابط Loose Coupling بين الخدمات. المخطط التالي يوضح معمارية قواعد البيانات على المشروع وكيف تكون:
لكل خدمة مصغرة قاعدة البيانات الخاصة بها، وكل خدمة تستطيع استخدام قاعدة البيانات المناسبة بها، وهذا يعرف ب Polyglot Persistence Architecture. مثلاً ال Driver Management والتي تبحث عن السائقين الأقرب للراكب، قد تستخدم قواعد بيانات تدعم ال Goe-Queries باستخدام ال GPS Location.
على السطح قد تبدوا ال Microservices مشابه لمفهوم ال SOA، فالاثنان يقسموا المشروع على شكل خدمات، لكن ال SOA لديها التعقيدات والبرتوكولات الخاصة بها Web Services Specification WS-* وال ESB، ولذلك ال Microservices هي أبسط وأخف وتعتمد على ال REST بدلاً من تلك البروتوكولات.
فوائد ال Microservices
هناك العديد من الفوائد في ال Microservices Architecture:
- أولاً: أنها تعالج مشكلة التعقيد Complexity حيث يتم تقسيم النظام الضخم Monolithic Application الى مجموعات من الخدمات يسهل العمل عليها وكل منها لديها حدود Boundaries وتواصل فيما بينهم بواسطة API (سواءً REST or RPC or Messaging API). واستخدامها سوف ينظم المشروع ويجعله Modular بشكل أفضل والذي يصعب تطبيقه في ال Monolithic. وبالرغم من أن المهام Functions هي نفسها، الا انه وصلنا لمجموعة من الخدمات التي يسهل تطويرها بسرعة وأسهل في الفهم والصيانة.
- ثانياً: هذه المعمارية تجعل تطوير كل خدمة مفصولة ومستقلة عن الأخرى، وبالتالي كل فريق أو مطور مسؤول عن الخدمة له الحرية في استخدام التقنيات المناسبة ويقوم بوضع ال Contract APIs لها. وبالطبع قد لا يكون الأمر متاحاً للمطورين لاختيار أي تقنية بشكل عشوائي ولكن ستكون لديهم مساحة من الحرية وغير مقيدين بأي تقنيات قديمة استخدمت سابقاً في خدمة أخرى. وأيضاً لأن الخدمات أصبحت صغيرة فيمكن فعلياً إعادة كتابتها من جديد بتقنية جديدة.
- ثالثاً: هذه المعمارية تسمح بأن يتم نشر Deploy الخدمات بشكل مستقل عن الأخرى، ولا يحتاجوا المطورين الى تنسيق بين كل الخدمات للتعديلات الداخلية في الخدمة والتي لا تؤثر على ال Exposed APIs. وهكذا يمكن نشرها مباشرة بمجرد الانتهاء من الاختبارات Testing وبالتالي ال Continuous Deployment أصبح ممكناً بسهولة هنا.
- رابعاً: هذه المعمارية تسمح لكل خدمة بأن يتم توسعتها Scaling بشكل مستقل وفعال، وكل خدمة قد يكون لديها متطلبات معينة، او حتى سيرفرات بمواصفات معينة لها، على سبيل المثال يمكن نشر الخدمات التي تقوم بعمل معالجة للصور والتي تأخذ وقتاً من المعالج CPU-Intensive على سيرفرات لديها أكثر من CPU Cores، والخدمات التي تتطلب ذاكرة أعلى في سيرفرات بذاكرة عالية.
عيوب ال Microservices
كما كتب فريد بروكس Fred Brooks منذ 30 عاماً “لا توجد رصاصة فضية There are no silver bullets”، وسوف نجد أن ال Microservices كما في غيرها من التقنيات بها بعض العيوب والمشاكل:
- أولاً: قد يكون اسمها Microservices يركز على الحجم وعدد الأسطر البرمجية يجب أن تكون قليلة، وهذا الأمر جيد، ولكن هي ليست الهدف النهائي، فالفكرة من الخدمات المصغرة هو تقسيم النظام الى أجزاء Decompose حتى يسهل العمل عليه ونشرها بشكل مرن Agile Application Development & Deployment.
- ثانياً: التعقيد الذي ينشئ منها بسبب أنها عدة موزعة ومستقلة، فيجب على المطورين اختيار طريقة التواصل Interprocess Communication سواءً باستخدام Messaging or RPC، وأيضاً معالجة المشاكل والفشل في حالة لم تعمل الخدمة الداخلية أو تأخرت التي استدعتها خدمة أخرى. هذه المشكلة ليست موجودة في ال Monolithic فهناك عملية الاستدعاء هي مجرد استدعاء دالة أخرى في نفس النظام Process.
- ثالثاً: من التحديات في الخدمات المصغرة هي أن بها أكثر من قواعد بيانات موزعة بداخل هذه الخدمات Partitioned Databases، وفي الوضع الطبيعي هناك العديد من العمليات Business Transactions تحتاج أن تقوم بالتعديل على عدة جداول في آن واحد، وهذا الأمر يسهل تطبيقه في ال Monolithic لأن هناك قاعدة بيانات واحدة، ولكن ال Microservices-Based Application فسوف تحتاج أن تقوم بالتعديل على عدة قواعد بيانات في عدة خدمات في آن واحد وهذا ما يضفي مزيد من التحديات للمطورين.
- رابعاً: اختبار هذه الخدمات المصغرة يحتاج المزيد من العمل، ففي ال Monolithic يمكن كتابة Test Class يقوم بتشغيل كامل التطبيق ومن ثم يقوم باختبار وظائفه وكل ال REST APIs فيه، أما في الخدمات المصغرة فسوف تحتاج أن تقوم بكتابة ال Test Class وتقوم بتشغيل الخدمة وكل الخدمات التي تحتاجها لكي تؤدي الخدمة المطلوبة (أو عمل Stubs/Mocks لها) وهذا يحتاج المزيد من العمل أيضاً.
- خامساً: من التحديات في الخدمات المصغرة وهو التعديلات التي تكون في أكثر من خدمة في آن واحد، مثلاً تريد تطبيق User Story وهي تتطلب تغير في الخدمة الأولى والثانية والثالثة، والخدمة الأولى تعتمد على الثانية، والثانية تعتمد على الثالثة. في ال Monolithic فهذا التعديل سهل للغاية فسوف تقوم بتغيير هذه الدوال في نفس المشروع ومن ثم رفعها جميعاً ونشرها على السيرفر. أما في ال Microservices فسوف تحتاج للتنسيق لكيفيه إتمام هذا التعديل ونشره، مثلاً تقوم بتعديل الخدمة الثالثة، وبعدها الثانية، وأخيراً الأولى. لحسن الحظ فطبيعة الخدمات المصغرة عادة لا يكون فيها هذا النوع من التعديلات، وغالباً معظم التعديلات تكون بداخل الخدمة نفسها فقط.
- سادساً: نشر الخدمات المصغرة أيضاً به المزيد من التعقيدات، فمثلاً في ال Monolithic فيتم نشر المشروع نفسه على عدة سيرفرات وأمامها يكون Load Balancer لتوزيع الطلبات عليها. أما هنا في ال Microservices فهناك العديد من الخدمات المصغرة (مثلاً Netflix لديها أكثر من 600 خدمة مصغرة)، وكل خدمة مصغرة سوف يكون لديها أكثر من نسخة Instances تعمل. وهذا يعني المزيد من النسخ التي تحتاج للإعداد Configure، النشر Deploy، التوسعة Scaling، المراقبة Monitoring. أيضاً قد تحتاج لعمل مستكشف للخدمات Service Discovery بحيث تستخدمها الخدمات لمعرفة تفاصيل الخدمات الأخرى (الروابط والمنافذ Hosts and Ports) التي تريد أن تتواصل معها، ومع هذا الحجم من التعقيدات فإدخال هذه البيانات يدوياً Hardcoded لن يجدي ويحتاج أيضاً Automations لها.
خلاصة
بناء الأنظمة والتطبيقات المعقدة أمر صعب ولا يستهان به، المعمارية الضخمة Monolithic هي مناسبة للتطبيقات البسيطة والصغيرة أو التجريبية MVPs. ولكنها ليست مناسبة للتطبيقات الكبيرة والتي تنمو سريعاً وستجد أنك أمام العديد من المشاكل حينها، وفي المقابل ال Microservices Architecture هي خيار أنسب للتطبيقات الكبيرة والتي تنمو سريعاً، الا أنها بها أيضاً تعقيدات وتحديات في تطبيقها.
في مقالات قادمة بإذن الله، سوف نتحدث عن مزيد من التفاصيل في عالم ال Microservices مثلاً إيجاد الخدمات Services Discovery، نشر الخدمات Service Deployment، وأيضاً طرق إعادة هيكلة Refactoring تطبيق موحد ضخم Monolithic الى Services.
المصادر
المصدر الأساسي هو مقالة Introduction to Microservices في مدونة NGINX وتم حذف بعض الجزئيات غير الضرورية والإعلانية مثلاً للمنتج NGINX Plus واضافة ما يلزم بحسب ما رأيته مناسباً.
ياريت تكمل مقالاتك عن ال Design patterns and Principles
مقال جميل ومفيد جدا
جزاك الله خيرا
مقال رائع و جميل بشرح كامل و مبسط…
Thank you very ,match for the great details
رائع جداً ما شاء الله