بقلم شفيع البيطار
مهندس معلوماتية
مقدمة
درجت العادة على بناء أي مشروع برمجي كتطبيق واحد Monolith Application بحيث يعتمد ذلك على بيئة تطوير محددة لتصميم الواجهات مثل (Visual Studio) ولغة برمجة مثل (C#, JAVA, PHP…) وقاعدة معطيات مثل (Oracle, SQL Server…)، ونتيجة لوجود محدودية للتوسع بالمشروع وملاحقة الأخطاء التي تزداد مع كبر حجم التطبيق فقد قامت عدة شركات كبرى مؤخرًا مثل Amazon, eBay, Netflix باعتماد تقنية الخدمات المصغرة التي يجري بواسطتها تقسيم المشروع إلى أصغر خدمات ممكنة (كل خدمة تقابل وظيفة معينة من وظائف المشروع) وتتصل هذه الخدمات بعضها ببعض Interconnected Services بطريقة يسهل بها توسيع المشروع (أي إضافة خدمات جديدة) ومعالجة المشاكل التي قد تطرأ على أداء أية خدمة.
نتحدث في هذه المقالة عن معمارية الخدمات المصغرة وميزاتها وتحدياتها.
بيئة الخدمات المصغرة
الخدمة المصغرة هي مقاربة هندسية تركز على تجزئة decomposing التطبيق إلى وحدات عمل مستقلة بعضها عن بعض، وكل وحدة module مسؤولة عن وظيفة محددة، وعندما تعمل هذه الوحدات معًا تُنتج التطبيق الأساسي. ولهذه الوحدات واجهات برمجية معرفة تعريفًا واضحًا للتواصل معها، ويجري تطويرها واختبارها وتشغيلها وترحيلها بطريقة مستقلة بواسطة فرق تطوير صغيرة [1]. ووفق هذه المقاربة يكون لكل خدمة مصغرة مهمة بسيطة، وتتواصل مع العملاء أو مع الخدمات الأخرى باستعمال آليات اتصال محددة.
يوضح الشكل الآتي طبقات بنية الخدمات المصغرة
وهذه الطبقات هي كما يلي:
- الطبقة الأولى: طبقة العميل، وتحتوي على أجهزة الحواسيب والجوالات والأجهزة اللوحية...
- الطبقة الثانية: تعيد توجيه طلبات العميل إلى الخدمات المناسبة عن طريق API
- الطبقة الثالثة: طبقة تجميع وتنظيم عمل الخدمات المصغرة داخل الحاويات، وتوجد كل خدمة من الخدمات المصغرة في حاوية
- الطبقة الرابعة: طبقة مخزن المعطيات
تعمل هذه الخدمات على مخدمات متعددة، ويكون التواصل فيما بينها إما ببروتوكولات متزامنة synchronous مثل:
HTTP/REST API, gRPC (high performance Remote Procedure Call) أو غير متزامنة asynchronous مثل: AMQP (Advanced Message Queuing Protocol) وMessage brokers [2]
الخدمات المصغرة والتطبيق الواحد
عند تصميم التطبيق كتلةً واحدة (exe, jar…)، تُبنى الواجهات GUI ومنطق الأعمال Business Logic وطبقة الاتصال مع قاعدة المعطيات Data Access Layer لكل وظائف التطبيق، أما في بنية الخدمات المصغرة فيجري تنظيم منطق الأعمال على أساس خدمات متعددة ومتصلة فيما بينها بحيث تؤدي كل خدمةٍ وظيفةً محددة ويمكن أن تتصل مع قاعدة معطيات مختلفة [3].
يبيِّن الشكل الآتي كيف يَعرض قسم بنية الخدمات المصغرة واجهةَ المستعمل التي تتصل بست خدمات مصغرة، اثنتان منها تتصل إحداهما بأخرى، والأربع الأخرى تتصل بمخازن معطيات مختلفة.
نوجز فيما يلي بعض الفروق بين بنية التطبيق الواحد وبنية الخدمات المصغرة [4]:
وهكذا نجد أنه إذا كان لدينا تطبيق واحد وكان هذا التطبيق ينمو إلى حدٍّ تصعب السيطرة عليه، وأن المشاكل بدأت بالتفاقم وأصبحنا غير قادرين على استعمال مكونات التطبيق عن طريق مشاريع أخرى أو على منصات عمل مختلفة أو إضافة ميزات جديدة إليه، فعلينا عند ذلك الانتقال إلى بنية الخدمة المصغرة. ولكن قبل الانتقال إلى هذه البنية علينا أن نسأل هذه الأسئلة المهمة:
- هل نستطيع أن نجري تغييرًا في خدمة ما، وننشرها وحدها دون الحاجة إلى إجراء تعديلات في الخدمات الأخرى؟
- هل نملك الموارد الكافية؟ لأننا سنحتاج إلى عدد أكبر من المخدمات لاستضافة الخدمات، وسنحتاج إلى ميزانية أكبر تكفي لسداد رواتب فرق التطوير والمهندسين الجدد.
- هل يملك المطورون الخبرة الكافية لتنجيز بنيان الخدمات المصغرة، وهل يملكون القدرة على نقل التطبيقات إلى بنية موزعة تعمل بكفاءة؟
- هل نملك خبراء تقنيين بمفهوم DDD (Domain Driven Design) الذي يرتكز على مفهوم نطاق الأعمال business domain ويتطلب فهمًا كاملًا لمكونات التطبيق وتصرفها، وآلية عمل الرماز الداخلي core الخاص به.
- ما هي القيمة المضافة لهذا العمل، وهل يستحق هذا الانتقال الجهد والمال المبذولين من أجله؟
اختبار الخدمات المصغرة
يجري تطوير الخدمات المصغرة في بيئة موزعة، ويتطلب ذلك إنشاء إطار اختبار شامل يتضمن طبقات متعددة من الاختبار، وذلك بهدف اكتشاف الأنواع المختلفة من الأخطاء، حيث يركز كل نوع من الاختبارات على طبقة مختلفة من جوهر النظام البرمجي، ويتحقق من سلوكه ضمن هذا النطاق.
في النظام الموزع مع user interface وbackend، يمكن تنظيم الاختبارات في الطبقات الآتية:
وهذه الطبقات هي كما يلي:
- Unit Tests هي اختبارات التحقق من إجرائيات الصف class methods أو الوظائف داخل الخدمة المصغرة. تأخذ هذه الاختبارات ملف الرماز للخدمة المصدر، وتختبر وظائفها باستعمال مدخَلات مختلفة، ثم تتحقق من نتيجة خرج هذه الوظائف.
- Integration Tests هي الاختبارات التي تتحقق من السلوك الخارجي لخدمة واحدة، حيث يبدأ إطار الاختبار بأخذ نسخة instance من الخدمة، ثم يستعمل الواجهة الخارجية للخدمة، من أجل تنفيذ بعض البرمجة logic عليها.
- End to End Tests هي الاختبارات التي تتحقق من سلوك الخدمات المتعددة التي تحتاج إلى أن تتواصل فيما بينها. يجري إنجاز هذه الاختبارات عن طريق تشغيل خدمات متعددة في بيئة معزولة تمكِّن من التواصل فعليًّا فيما بينها.
- UI Tests هي الاختبارات التي تتحقق من سلوك النظام الأساسي بتمامه، ويجري ذلك باختبار التواصل بين واجهة المستعمل user interface والنظام البرمجي الموجود على المخدم backend.
نشر الخدمات المصغرة
بعد بناء الخدمة المصغرة واختبارها ينبغي وضعها في حاوية. والحاوية هي وحدة برمجيات موحدة تُستعمل لتطوير التطبيقات ونشرها. ولا تؤثر الخدمة المصغرة الجاري وضعها في الحاوية الخاصة بها على الخدمات المصغرة المبثوثة في هذه الحاوية. وتُدار الحاويات باستعمال محرك حاوية مثل Docker الذي يوفر جميع الأدوات اللازمة لتجميع كل تبعيات التطبيق في الحاوية.
تتطلب عملية وضع خدمة مصغرة في الحاوية تكوين ملف Dockerfile، ثم إنشاء صورة image تتضمن تبعيات هذه الخدمة، ثم توزيع الصورة على المحرك Docker Container، وأخيرًا تحميل الصورة على سجل حاويات Container Registry للتخزين والاسترجاع.
يضمن نشر الخدمات المصغرة في الحاويات تحقيق خاصية تسمى قابلية التوسع Scalability، وفي هذا الإطار قام مؤلف كتاب Art of Scalability بطرح فكرة مكعب ثلاثي الأبعاد Scale Cube - وهو نموذج لتمثيل فكرة التوسع - حيث ذكر أن هناك ثلاثة أبعاد لتحقيق هذه الفكرة هي [5]:
- البعد الأول X-axis، وهو نشر عدة نسخ من نفس البرنامج Load Balancer
- البعد الثاني Y-axis، وهو تقسيم المهام إلى خدمات مصغرة
- البعد الثالث Z-axis، وهو تقسيم المعطيات، وهذا البعد هجين يزاوج بين البعدين الأول والثاني، حيث تُنشر نفس النسخة في عدة مخدمات، ولكن لكل منها مجموعة خاصة من المعطيات، ويكون هناك جزء في المشروع يحدد أين يذهب الطلب وإلى أي مخدم توجد فيه المعطيات، وذلك بالاستفادة من معطيات الطلب؛ مثل: رقم العميل، أو أية معلومات أخرى.
قد تَستعمل التطبيقات هذه الأبعاد الثلاثة معًا، فيجري استعمال البعد Y-axis ويتم عمل التوسع scaling بتقسيم المشروع إلى خدمات مصغرة، وفي وقت التشغيل runtime يُستعمل البعد X-axis عن طريق نشر كل خدمة أكثر من مرة، وذلك لتحقيق مزيد من الإنتاجية والتوفر Throughput and Availability، وقد تَستعمل بعض التطبيقات البعد Z-axis في بعض الحالات التي تترافق مع التوسع في قاعدة المعطيات Database Scaling.
ميزات الخدمات المصغرة
ثمة فوائد عديدة في معمارية الخدمات المصغرة؛ منها:
- قد تبدو الخدمات المصغرة مشابهةً لمفهوم SOA من ناحية تقسيم المشروع إلى مجموعة من الخدمات، لكن SOA لديها التعقيدات والبرتوكولات الخاصة بها Web Services Specification ، أما الخدمات المصغرة فهي أبسط وأخف وتعتمد على تقنية REST بدلًا من تلك البروتوكولات.
- الخدمة المصغرة تعالج مجالًا وظيفيًّا معيَّنًا وتوفر واجهةً للاتصال مع الخدمات الأخرى، ولديها قاعدة معطيات وفريق تطوير خاص بها يكون مسؤولًا عن دورة حياة الخدمة كاملةً (coding, testing, staging deploying, debugging, maintaining).
- عدد الأسطر البرمجية في الخدمة المصغرة قليل نسبيًّا، وهذا جيد للاختبار والنشر بمرونة Agile Application Development & Deployment
- يمكن نشر الخدمات بشكل مستقل، وكذلك يمكن بناء وإعادة ترحيل كل خدمة على حدة بشكل مستقل عن الخدمات الأخرى ودون الحاجة إلى ترحيل كامل النظام.
- لا تحتاج الخدمات المصغرة لأن تعمل بنفس التقنيات أو على نفس البيئة البرمجية، وقد يَستعمل كلٌّ منها لغة برمجة مختلفة أو قاعدة معطيات من نوع مختلف SQL, No SQL
- إذا توقفت خدمة ما عن العمل، فلن يؤدي ذلك إلى توقف التطبيق كله عن العمل Fault isolation
- يمكن ترحيل الخدمات المصغرة إلى السحابة Cloud بشكل منفصل، وهذا يعطي إمكان تخصيص موارد لكل خدمة بالقدر الذي تطلبه، مما يؤدي إلى استعمال أمثل للموارد Granular scaling
- هذه المعمارية تسمح لكل خدمة بالتوسع Scaling بشكل مستقل وفعال، إذ إن لكل خدمة متطلبات معيَّنة، أو حتى مخدمات بمواصفات معينة. على سبيل المثال، يمكن نشر الخدمات التي تعالج الصور وتحتاج إلى أخذ وقت من المعالج CPU-Intensive على مخدمات فيها أكثر من معالج CPU Cores، والخدمات التي تتطلب ذاكرة أعلى تُنشر على مخدمات بذاكرة عالية.
تحديات الخدمات المصغرة
الخدمات المصغرة كغيرها من التقنيات فيها بعض العيوب والمشاكل؛ نذكر منها:
- التعقيد Complexity: يتضمن تطبيق الخدمات المصغرة عددًا من الأجزاء، فإذا ازداد هذا العدد أصبح من الصعب السيطرة على التطبيق من مكان واحد. وبسبب كون هذه الأجزاء موزعة ومستقلة، فإن على المطورين اختيار طريقة التواصل Interprocess Communication باستعمال Messaging أو RPC، ومعالجة المشاكل والإخفاق إذا لم تعمل إحدى الخدمات أو تأخرت في الاستجابة latency.
- الاختناقات الشبكية Network congestion: إن استعمال العديد من الخدمات المصغرة قد يؤدي إلى ازدحام شبكي وتأخير في معالجة الطلبات بسبب كثرة التواصل بين خدمات التطبيق الموزع.
- مشكلة الاعتماديات Dependencies: قد تحتاج إحدى الخدمات المصغرة - عند البدء - إلى الحصول على معلومات من خدمة أخرى، وهذه الخدمة قد تعتمد على تلك الخدمة في المقابل.
- الإدارة Management: إن إدارة ومراقبة جميع الخدمات هي بحد ذاتها تحدّ كبير، ولذلك ينبغي أن يلتزم المطورون بثقافة التطوير والتشغيل DevOps وتوليد ملفات الأثر Logging لكل خدمة.
- الإصدارات Versioning: إن تحديث الخدمات بطريقة فوضوية قد يقود إلى إخفاق في التواصل فيما بين الخدمات.
- مهارات التطوير Skillset: إن اعتماد بنيان الخدمات المصغرة هو بحد ذاته تحدّ لفريق التطوير، وليس كل مطور لديه المهارات اللازمة والخبرة الكافية لتنجيز ذلك بالطريقة الصحيحة.
- قواعد المعطيات Database: في الخدمات المصغرة تكون قواعد المعطيات مجزأة Partitioned، ويكون هناك العديد من العمليات Business Transactions التي تحتاج إلى التعديل في عدة جداول في آن واحد، وهذا يضفي مزيدًا من التحديات للمطورين.
- الاختبارات Testing: إن اختبار الخدمات المصغرة يحتاج إلى الكثير من العمل. ففي حالة التطبيق الواحد يمكن كتابة Test Class الذي يشغِّل التطبيق كله ومن ثم يقوم باختبار وظائفه، أما في الخدمات المصغرة فنحتاج إلى كتابة Test Class لكل خدمة واختبار وظائفها وتواصلها مع باقي الخدمات.
- النشر Deploy: إن نشر الخدمات المصغرة يتضمن أيضًا المزيد من التعقيدات؛ ففي التطبيق الواحد ينشر المشروع نفسه على عدة مخدمات، ويكون لدينا Load Balancer لتوزيع الطلبات عليها. أما في الخدمات المصغرة فإن كل خدمة سوف يكون لديها أكثر من نسخة Instance تعمل، وهذا يعني المزيد من النسخ التي تحتاج للإعداد Configure، والتوسعة Scaling، والمراقبة Monitoring. وقد نحتاج أيضًا إلى عمل مستكشف للخدمات Service Discovery بحيث تستعملها خدمة ما لمعرفة تفاصيل خدمات أخرى (الروابط والمنافذ Hosts and Ports) تريد أن تتواصل معها. وإضافة إلى هذا الحجم من التعقيدات، فإن إدخال هذه المعطيات يدويًّا Hardcoded لن يجدي نفعًا ويحتاج أيضًا إلى عملية أتمتة Automation.
الخاتمة
إن اعتماد معمارية الخدمات المصغرة لبناء التطبيقات البرمجية يجعل نظامنا أقوى وأكثر قابلية للتوسع والتطوير في المستقبل، ولن تتأثر سرعة الاستجابة للتطبيق حتى بوجود عدد كبير من الزبائن، لأن البنية ستحميه من الإخفاق ولو توقفت إحدى الخدمات المكونة له. وكذلك فإن اعتماد هذه المعمارية يمنح النظام مرونة في البناء، فكل خدمة يمكن بناؤها بتقنية مختلفة، قد تكون هي الأنسب لتنفيذ هذه الخدمة، ويمكن نشر كل خدمة على موارد مختلفة قد تكون هي الأكثر فعالية لعمل هذه الخدمة .
المراجع
[1] Fowler, “Microservices: a definition of this new architectural term.”, https://martinfowler.com/articles/microservices.html , 2014
[2] Raycad, “Designing interservice communication for microservices” , https://docs.microsoft.com/en-us/azure/architecture/microservices/design/interservice-communication , 2019
[3] Sam Newman, “Building Microservices” , http://ce.sharif.edu/courses/96-97/1/ce924-1/resources/root/Books/building-microservices-designing-fine-grained-systems , 2015
[4] Raycad, “Monolithic vs microservice architecture” , https://medium.com/@raycad.seedotech/monolithic-vs-microservice-architecture-e74bd951fc14 , 2018
[5] https://microservices.io/articles/scalecube.html