بنية الخدمات المصغّرة: من التطبيقات المتجانسة إلى أنظمة قابلة للتوسّع ويمكن نشرها باستقلال
تقرير من قبل
Muhammed Mishal

من التطبيقات المتجانسة إلى أنظمة قابلة للتوسّع ويمكن نشرها باستقلال
مقال تقني عملي للمطوّرين والمعماريين وفرق الهندسة
الصورة العامة
بنية الخدمات المصغّرة (Microservices Architecture) هي أسلوب لتصميم البرمجيات كمجموعة من الخدمات الصغيرة المركّزة القابلة للنشر باستقلال. فبدلًا من وضع كل قدرة تجارية داخل تطبيق واحد كبير، يُقسَّم النظام حول مسؤوليات تجارية ذات معنى مثل المستخدمين والطلبات والمدفوعات والمخزون والبحث أو الإشعارات. وتعرض كل خدمة عقدًا واضحًا وتُخفي تفاصيل تنفيذها الداخلي عن بقية أجزاء المنصة.
الفكرة المهمة ليست مجرد إنشاء الكثير من تطبيقات الخلفية. فالتغيير المعماري الحقيقي هو إرساء حدود واضحة للملكية. ينبغي أن تملك الخدمة قدرة ذات معنى، وقواعد عملها، وعادةً البيانات اللازمة لدعم تلك القدرة. ثم تتعاون الخدمات عبر واجهات برمجية (API) أو أحداث غير متزامنة. وتؤكد إرشادات البنية الحديثة بالمثل على النشر المستقل والواجهات جيدة التحديد والحالة التي تملكها الخدمة.
لماذا أصبحت الخدمات المصغّرة مهمة
كثيرًا ما يكون التطبيق المتجانس (Monolith) نقطة انطلاق ممتازة. فهو سهل نسبيًا في التشغيل والتصحيح والاختبار والنشر لأن التطبيق موجود كوحدة رئيسية واحدة. وتظهر المشكلات حين ينمو المنتج. تبدأ المجالات التجارية المختلفة بالتغيّر بسرعات مختلفة، ويحتاج أحد الأجزاء إلى طاقة حوسبة أكبر من غيره، ويصبح النشر محفوفًا بالمخاطر، ويبدأ فريق التطوير المتنامي بالتزاحم على قاعدة الشيفرة نفسها.
تعالج الخدمات المصغّرة هذه الضغوط بالسماح لكل قدرة تجارية بالتطور بمعزل عن غيرها. يمكن نشر خدمة المدفوعات دون إعادة بناء خدمة المخزون. ويمكن توسيع خدمة الكتالوج ذات الحركة العالية دون إنشاء العدد نفسه من نسخ خدمة المدفوعات. كما تستطيع الفرق تولّي ملكية أوضح لكل خدمة. لكن المقابل هو أن التطبيق يصبح نظامًا موزّعًا، ولذلك يجب تصميم الشبكات والأعطال والاتساق والمراقبة والنشر بصورة صريحة.
التطبيق المتجانس والخدمات المصغّرة: التحول المعماري
يسهل فهم الفرق أكثر عند رؤيته بصريًا. ففي التطبيق المتجانس التقليدي، تنتمي شيفرة واجهة المستخدم ومنطق العمل ووحدات الأعمال المتعددة عادةً إلى تطبيق واحد قابل للنشر، وتتعامل غالبًا مع قاعدة بيانات مشتركة. أما في نظام الخدمات المصغّرة فتُفصل هذه القدرات إلى خدمات قابلة للنشر باستقلال. ويمكن لكل خدمة أن تملك قاعدة بيانات أو حدًّا آخر واضح التحكم للتخزين.
لا يعني هذا أن كل وظيفة تستحق خدمة خاصة بها. فالتجزئة المفرطة تخلق المشكلة المعاكسة: عمليات نشر كثيرة جدًا، واستدعاءات شبكة كثيرة جدًا، وتنسيق أكثر من اللازم. والهدف هو إيجاد حدود تمثّل قدرات تجارية مستقرة وتتيح تغييرًا مستقلًا ذا معنى.
كيف يُبنى نظام الخدمات المصغّرة
يبدأ النظام الإنتاجي عادةً من تطبيق ويب أو تطبيق جوال أو عميل خارجي. تدخل الطلبات عبر طبقة حافة، غالبًا بوابة API (API Gateway) أو موزّع أحمال. توفّر البوابة نقطة دخول خارجية ثابتة بينما يمكن لخدمات الخلفية أن تتغير داخليًا. ويمكنها أيضًا المشاركة في فرض المصادقة والتوجيه وتحديد معدل الطلبات وإنهاء TLS وتحويل الطلبات وغيرها من الاهتمامات العابرة. وتصف مايكروسوفت بوابة API بأنها نقطة دخول مركزية للتفاعل بين العملاء وخدمات التطبيق.
خلف البوابة توجد خدمات المجال. فمنصة التجارة الإلكترونية مثلًا قد تضم قدرات العملاء والكتالوج والسلة والطلبات والمدفوعات والمخزون والتوصيل والإشعارات. وتعتمد الحدود الدقيقة على المجال التجاري. وتتجنب أقوى التصاميم كشف هياكل قواعد البيانات الداخلية، وتتواصل بدلًا من ذلك عبر واجهات مستقرة.
حدود الخدمات أهم من عددها
أصعب قرار في الخدمات المصغّرة هو تحديد أين تنتهي خدمة وأين تبدأ أخرى. الحد المفيد يبقي قواعد العمل المترابطة معًا. فإذا كانت عملية التحقق من الطلب وانتقالات حالة الطلب وقواعد العمل الخاصة بالطلبات موزّعة باستمرار على خمس خدمات، يصبح النظام صعب الفهم. وفي المقابل، إذا وُضعت الطلبات والمدفوعات والمخزون والإشعارات كلها في خدمة عملاقة واحدة، فإن الفريق يكون قد أعاد إنشاء تطبيق متجانس باسم مختلف.
يفيد التصميم الموجَّه بالمجال (Domain-Driven Design) كثيرًا هنا لأنه يشجّع الفرق على تحديد القدرات التجارية والسياقات المحدودة (Bounded Contexts) قبل تقرير بنية الخدمات التقنية. ينبغي أن يكون السؤال: «أي قدرة تجارية تحتاج إلى ملكية مستقلة؟» بدلًا من: «كم واجهة برمجية نستطيع إنشاءها؟»
كيف تتواصل الخدمات
تتواصل الخدمات المصغّرة أساسًا عبر طلبات متزامنة أو رسائل غير متزامنة. يناسب التواصل المتزامن عبر HTTP أو gRPC الحالات التي يحتاج فيها المتصل إلى نتيجة فورية. فعلى سبيل المثال، قد تسأل خدمة الطلبات خدمةَ المخزون بشكل متزامن عمّا إذا كان يمكن حجز منتج ما.
أما التواصل غير المتزامن فمختلف. فبدلًا من انتظار انتهاء خدمة أخرى، تنشر الخدمة حدثًا إلى وسيط رسائل (Message Broker). وتستهلك الخدمات الأخرى ذلك الحدث حين تكون جاهزة. يقلّل هذا الأسلوب الترابط المباشر، وهو مفيد بشكل خاص للإشعارات والتحليلات وسير عمل التكامل وسائر العمليات التي لا تحتاج إلى حجب الطلب الأصلي.
يُدخل الأسلوب غير المتزامن مفهوم الاتساق النهائي (Eventual Consistency). فقد يُقدِّم المستخدم طلبًا قبل أن تنتهي كل الخدمات اللاحقة من معالجته. وهذا ليس مشكلة بالضرورة، لكن على فرق المنتج والهندسة أن تحدّد صراحةً الحالات التي يمكن أن يكون عليها الطلب، وما يحدث عند فشل إحدى الخطوات اللاحقة.
ملكية البيانات وفكرة قاعدة بيانات لكل خدمة
من أهم القواعد في الخدمات المصغّرة ملكية البيانات. ينبغي عادةً أن تتحكم الخدمة في البيانات التي تنتمي إلى قدرتها التجارية. وعلى الخدمات الأخرى الوصول إلى تلك المعلومات عبر واجهة برمجية أو حدث، لا عبر تحديث جداول خدمة أخرى مباشرةً.
لا يتطلب هذا أن تستخدم كل خدمة منتج قاعدة بيانات مختلفًا تمامًا. فقد يستخدم الفريق تقنية قاعدة البيانات نفسها عبر عدة خدمات مع الحفاظ على ملكية منطقية وحدود وصول. والهدف الأساسي هو تفادي وضع تستطيع فيه كل خدمة تعديل أي جدول بحرية. فهذا النوع من الملكية المشتركة يعيد الترابط الوثيق ويصعّب النشر المستقل.
كما أن المعاملات الموزّعة أصعب من المعاملات داخل تطبيق واحد. وفي سير العمل الممتد عبر عدة خدمات، يمكن استخدام أنماط مثل ساغا (Saga) وصندوق الصادر المعاملاتي (Transactional Outbox) والمستهلكين عديمي الأثر عند التكرار (Idempotent Consumers) وإعادة المحاولة والإجراءات التعويضية للحفاظ على الاتساق التجاري.
الأمان في بيئة الخدمات المصغّرة
يجب أن يوجد الأمان في طبقات متعددة. تستطيع البوابة التحقق من رموز المصادقة وتطبيق سياسات عامة، لكن على كل خدمة أن تفرض التفويض للموارد التي تملكها. ولا ينبغي لأي خدمة أن تفترض أن الطلب آمن لمجرد أنه مرّ عبر البوابة.
ينبغي للأنظمة الإنتاجية حماية بيانات الاعتماد والأسرار، والتحقق من صحة البيانات الواردة، واستخدام اتصال مشفّر عند الاقتضاء، وتطبيق حدود للمعدل على الواجهات العامة، والحفاظ على هويات واضحة للخدمات. ويزداد الأمان أهمية مع زيادة عدد الحدود الشبكية، لأن كل مسار اتصال إضافي يصبح جزءًا من سطح الهجوم على النظام.
المرونة: التصميم مع توقّع الأعطال
عادةً ما يفشل استدعاء الدالة داخل التطبيق المتجانس في العملية نفسها. أما في الخدمات المصغّرة فقد تعبر العملية نفسها عدة شبكات وعمليات مستقلة. وقد تكون الخدمة سليمة بينما تابعتها غير متاحة أو بطيئة أو تعيد أخطاء. لذلك فالفشل حالة معمارية طبيعية وليس وضعًا استثنائيًا.
تمنع مهل الانتظار (Timeouts) تبعيةً غير متاحة من احتجاز الموارد إلى أجل غير مسمّى. ويمكن لإعادة المحاولة أن تتعافى من الأعطال العابرة، لكن ينبغي أن تكون محدودة وأن تستخدم التراجع التدريجي (Backoff) حتى لا تُغرَق خدمة متعثّرة. ويمكن لقواطع الدائرة (Circuit Breakers) إيقاف الاستدعاءات مؤقتًا إلى تابعة فاشلة، بينما تعزل الحواجز (Bulkheads) الموارد بحيث لا يستهلك حمل عمل واحد كل ما هو متاح للخدمة.
وعدم تأثر النتيجة بالتكرار (Idempotency) لا يقل أهمية. فإذا أُعيدت محاولة طلب دفع بسبب فشل الشبكة، ينبغي أن يتمكن النظام من التعرف على العملية المكرّرة وتفادي خصم المبلغ من العميل مرتين. هذه القرارات التصميمية الصغيرة هي ما يجعل النظام الموزّع موثوقًا في الواقع العملي.
قابلية المراقبة ليست خيارًا
مع كثرة الخدمات، قد يمر إجراء واحد من المستخدم عبر البوابة وطبقة المصادقة وخدمة الطلبات وخدمة المدفوعات ووسيط الرسائل وخدمة المخزون وخدمة الإشعارات. ولم يعد النظر في ملف سجل خدمة واحدة كافيًا لفهم الطلب كاملًا.
لذلك تجمع المنصة الناضجة بين سجلات مركزية منظّمة ومقاييس وتتبّع موزّع (Distributed Tracing) وفحوصات صحة وتنبيهات قابلة للتنفيذ. وتتيح معرّفات الارتباط (Correlation IDs) للمهندسين تتبّع الطلب عبر الخدمات. وتكشف المقاييس زمن الاستجابة وحجم الحركة ومعدلات الأخطاء وتشبّع الموارد. أما التتبّع الموزّع فيُظهر أين يُقضى الوقت وأي تابعة تسبب التباطؤ.
اختبار نظام موزّع
يتطلب اختبار الخدمات المصغّرة أكثر من اختبارات الوحدة. تظل اختبارات الوحدة مهمة لقواعد العمل، لكن الخدمات تحتاج أيضًا إلى اختبارات تكامل لقواعد البيانات والتبعيات الخارجية، واختبارات واجهات برمجية للعقود العامة، واختبارات عقود (Contract Tests) للتأكد من اتفاق المُنتِجين والمستهلكين على شكل الطلبات والأحداث ومعناها.
كما ينبغي اختبار رحلات العملاء الحرجة من طرف إلى طرف. ويلزم اختبار الأداء للخدمات عالية الحجم، بينما ينبغي أن يحاكي اختبار المرونة انتهاء المهل وأعطال التبعيات والرسائل المكرّرة والانقطاعات الجزئية. والغرض ليس إثبات أن الفشل لا يحدث أبدًا؛ بل التحقق من أن النظام يتصرف بصورة متوقعة حين يحدث الفشل.
التكامل والنشر المستمران والنشر المستقل
النشر المستقل من الأسباب الرئيسية التي تدفع المؤسسات إلى اعتماد الخدمات المصغّرة. ينبغي أن يكون لكل خدمة خط أنابيب قابل للتكرار يبني الشيفرة، وينفّذ الاختبارات الآلية، ويجري فحوصات الأمان، ويعبّئ الخدمة، وينشرها، ويتحقق من سلامتها، ويدعم التراجع عند الحاجة.
تُستخدم الحاويات (Containers) كثيرًا لتعبئة الخدمات بصورة متسقة عبر البيئات. ثم تتولى منصات التنسيق الجدولة والتوسّع واكتشاف الخدمات وفحوصات الصحة وعمليات النشر المتدرجة. ويمكن لاستراتيجيات النشر مثل الإصدار الكناري (Canary) والأزرق-الأخضر (Blue-Green) تقليل مخاطر الإنتاج بحصر عدد المستخدمين المعرّضين للإصدار الجديد.
التكلفة الخفية للخدمات المصغّرة
لا تزيل الخدمات المصغّرة التعقيد، بل تنقله من تنظيم الشيفرة إلى تنسيق النظام. فبدلًا من عملية تطبيق واحدة، يشغّل الفريق الآن عمليات كثيرة. وبدلًا من استدعاء دالة محلي، قد يكون هناك طلب شبكي. وبدلًا من معاملة واحدة، قد يكون هناك سير عمل موزّع. وبدلًا من مجرى سجلات واحد، تلزم مراقبة مركزية.
وهذا يعني أن الخدمات المصغّرة قد تكون خيارًا سيئًا لمنتج صغير أو فريق صغير أو نظام لم يستقر مجاله بعد. فالتطبيق المتجانس المعياري (Modular Monolith) يمكن أن يوفّر فصلًا قويًا للمسؤوليات مع بقائه بسيطًا تشغيليًا. ويمكن لاحقًا استخراج الخدمات حين يوجد سبب واضح يتعلق بالتوسّع أو النشر أو التنظيم أو الموثوقية.
مثال عملي: معالجة الطلبات
تخيّل سوقًا إلكترونية يقدّم فيها عميل طلبًا. يصل الطلب أولًا إلى بوابة API ويُوجَّه إلى خدمة الطلبات. تتحقق خدمة الطلبات من صحة الطلب وتنشئ طلبًا في مخزن بياناتها الخاص. ثم تنشر حدث OrderCreated.
تستهلك خدمة المدفوعات الحدث وتنفّذ سير عمل الدفع. وتحجز خدمة المخزون الأصناف المطلوبة، بينما تجهّز خدمة الإشعارات رسالة بريد إلكتروني أو إشعارًا فوريًا. ولا تحتاج هذه الخدمات إلى التنفيذ كمعاملة كبيرة واحدة. فهي تحتفظ بحالتها الخاصة وتتواصل عبر عقود محددة جيدًا.
إذا فشل الدفع، يمكن أن ينتقل الطلب إلى حالة «فشل الدفع». وإذا تعذّر حجز المخزون بعد نجاح الدفع، فقد يُطلق النظام إجراء دفع تعويضيًا أو يحيل الطلب إلى مراجعة يدوية، بحسب قواعد العمل. وهكذا تجعل البنية معالجة الفشل جزءًا صريحًا من سير العمل بدلًا من إخفائها داخل معاملة كبيرة واحدة.
متى تختار الخدمات المصغّرة؟
الخدمات المصغّرة خيار قوي حين يتضمن المنتج حدودًا تجارية واضحة، أو عدة فرق تطوير، أو متطلبات توسّع مختلفة، أو إصدارات مستقلة متكررة، أو أحمال عمل تحتاج إلى عزل قوي. وهي مفيدة أيضًا حين يجب أن تتطور قدرات معينة بإيقاع مختلف عن بقية المنصة.
لكنها ليست تلقائيًا أفضل بنية. فإذا كان التطبيق صغيرًا والفريق صغيرًا والنشر بسيطًا والمجال ما زال يتغير بسرعة، فقد يكون التطبيق المتجانس المعياري جيد التنظيم أكثر اقتصادية. وينبغي أن تتبع البنية المشكلةَ لا الموضة.
الخلاصة
بنية الخدمات المصغّرة تدور في جوهرها حول الاستقلال المنضبط. الهدف هو منح الفرق والقدرات التجارية فصلًا كافيًا لتتغير وتُنشر وتتوسّع وتتعافى دون أن تضطر المنصة كلها إلى التحرك كوحدة واحدة. وتجمع أقوى الأنظمة بين حدود خدمات واضحة، وواجهات برمجية منضبطة، وبيانات مملوكة، وأحداث غير متزامنة حيث يلزم، واتصال مرن، وأمان قوي، وتسليم آلي، ومراقبة عميقة.
لذلك فإن أنجح منصة خدمات مصغّرة ليست تلك التي تضم أكبر عدد من الخدمات، بل تلك التي لكل خدمة فيها سبب واضح لوجودها، ومالك واضح، وعقد واضح، وقصة تشغيلية واضحة.
ملاحظات مرجعية
في مناقشة البنية، جرت الاستعانة بإرشادات معمارية عامة حديثة، منها توثيق مايكروسوفت أزور (Microsoft Azure) للخدمات المصغّرة وبوابة API.