معمارية الخدمات المصغّرة وتصميمها

الأنماط والأنماط المضادة

18 دقيقة الدرس 9 من 12

الأنماط والأنماط المضادة

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

نمط الشجرة الخانقة (Strangler Fig)

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

في Spring Cloud Gateway، يُعبَّر عن طبقة التوجيه ببساطة في YAML:

# application.yml — إعداد Strangler عبر Spring Cloud Gateway spring: cloud: gateway: routes: - id: orders-service uri: lb://orders-service # توجيه إلى الخدمة المصغّرة الجديدة predicates: - Path=/api/orders/** - id: legacy-monolith uri: http://legacy.internal:8080 # كل شيء آخر يذهب إلى التطبيق القديم predicates: - Path=/**

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

نمط السيارة الجانبية (Sidecar)

لا ينبغي أن تسكن الاهتمامات المتقاطعة كـ mTLS والتتبع الموزع وإعادة المحاولات في كود تطبيق كل خدمة. ينشر الـ Sidecar حاوية مساعدة بجانب كل مثيل خدمة — في نفس الـ Pod في Kubernetes — تتولى بشفافية تلك الاهتمامات عبر اعتراض حركة الشبكة.

Sidecar مقابل مكتبة مشتركة: تُلزم المكتبة المشتركة كل الفرق بالترقية معًا وتربط لغة التشغيل. الـ Sidecar محايد للغة وقابل للترقية باستقلالية ويُبقي كود الخدمة مركَّزًا على منطق الأعمال. لهذا تُبنى شبكات الخدمات (Istio، Linkerd) على نموذج الـ Sidecar.

نمط قاطع الدائرة (Circuit Breaker)

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

مع Resilience4j في Spring Boot 3:

// تبعية pom.xml // <artifactId>resilience4j-spring-boot3</artifactId> @Service public class InventoryClient { @CircuitBreaker(name = "inventoryService", fallbackMethod = "defaultInventory") public InventoryResponse checkStock(String productId) { return restClient.get() .uri("/inventory/{id}", productId) .retrieve() .body(InventoryResponse.class); } private InventoryResponse defaultInventory(String productId, Throwable t) { // إعادة استجابة آمنة متدهورة بدلًا من إشاعة الفشل return InventoryResponse.unavailable(productId); } }
# application.yml — عتبات قاطع الدائرة resilience4j: circuitbreaker: instances: inventoryService: sliding-window-size: 10 failure-rate-threshold: 50 # فتح الدائرة بعد فشل 50% من آخر 10 استدعاءات wait-duration-in-open-state: 10s permitted-number-of-calls-in-half-open-state: 3

نمط Saga (كنمط معماري)

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

الكوريوغرافيا مقابل الأوركسترا: في Saga المُصمَّمة بالكوريوغرافيا تستجيب كل خدمة للأحداث باستقلالية — اقتران منخفض لكن يصعب فهمه. في Saga المُصمَّمة بالأوركسترا يتولى منسق مركزي (مثل آلة حالات Spring أو إطار Axon) إصدار الأوامر لكل مشارك — أسهل في التتبع لكنه نقطة تحكم محتملة. افضل الأوركسترا عندما تتجاوز Saga ثلاث خطوات أو عندما يكون مسار التدقيق متطلبًا تنظيميًا.

طبقة مكافحة التلوث (Anti-Corruption Layer)

عند التكامل مع نظام قديم أو خدمة طرف ثالث تستخدم نموذج مجال مختلفًا، لا تدع ذلك النموذج الخارجي يتسرب إلى سياقك المحدود. أدخل طبقة مكافحة التلوث (ACL) — حد ترجمة يُحوّل البيانات الواردة إلى نموذجك الخاص والبيانات الصادرة إلى نموذجهم.

// ACL: ترجمة جهة اتصال CRM القديمة إلى نموذج Customer الخاص بك @Component public class CrmContactTranslator { public Customer fromLegacy(LegacyCrmContact contact) { return new Customer( new CustomerId(contact.getContactId()), new FullName(contact.getFirstName(), contact.getLastName()), Email.of(contact.getEmailAddress()) ); } public LegacyCrmContact toLegacy(Customer customer) { LegacyCrmContact contact = new LegacyCrmContact(); contact.setContactId(customer.getId().value()); contact.setFirstName(customer.getName().first()); contact.setLastName(customer.getName().last()); contact.setEmailAddress(customer.getEmail().value()); return contact; } }

تضمن ACL أن التغيير في نموذج النظام القديم يستلزم تعديلًا في المترجم فقط، لا في كامل خدمتك.

نمط التوحيد الموزع المضاد (Distributed Monolith)

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

أعراض التوحيد الموزع:

  • قاعدة بيانات مشتركة: تقرأ خدمات متعددة وتكتب في نفس الجداول. يتطلب تغيير المخطط تنسيق فرق متعددة.
  • سلاسل متزامنة: الخدمة A تستدعي B التي تستدعي C التي تستدعي D في طلب واحد. أي عقدة تتوقف تُسقط السلسلة بأكملها.
  • واجهات ثرثارة: عشرة استدعاءات REST دقيقة للبيانات التي يمكن تجميعها في خدمة واحدة، مما يُضيف زمن استجابة وسطح فشل على كل طلب.
  • كائنات مجال مشتركة: مكتبة dto أو model مشتركة تستوردها كل خدمة. تغيير حقل يستلزم إصدار جميع المستهلكين ونشرهم في آنٍ واحد.
المكتبات المشتركة قنبلة اقتران موقوتة. ملف JAR باسم common-models يحمل كائنات المجال عبر حدود الخدمات هو أكثر الطرق شيوعًا لتحويل مشروع الخدمات المصغّرة خفيةً إلى توحيد موزع. اقبل بعض التكرار — DTO باسم UserSummary في كل خدمة — بدلًا من مشاركة كيان User عبر حدود الشبكة.

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

نمط واجهة API الثرثارة المضاد (Chatty API)

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

  • تجميع خشن الحبيبات: أضف نقطة نهاية تُعيد كل البيانات التي يحتاجها المستهلك في استدعاء واحد.
  • GraphQL أو BFF (خلفية للواجهة الأمامية): خدمة BFF مخصصة (في الغالب واحدة لكل نوع عميل — ويب، موبايل) تجمّع البيانات من خدمات متعددة من جانب الخادم وتُعيدها في حمولة واحدة، مما يُلغي التشعيع من جانب العميل.
  • إسقاط حدث المجال: اعرض البيانات التي يحتاجها المستهلك في نموذج قراءة خاص به (جانب القراءة في CQRS) حتى يتمكن من الرد على الاستعلامات محليًا.

تحديد تفكيك خدمتك بنفسك

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

الخلاصة

الأنماط التي تناولناها هنا — الشجرة الخانقة، الـ Sidecar، قاطع الدائرة، Saga، وطبقة مكافحة التلوث — يحل كل منها فئة محددة من المشكلات. التوحيد الموزع وواجهة API الثرثارة هما نمطا الفشل اللذان تصطدم بهما معظم الفرق أولًا. يجمع الدرس الأخير من هذه الدورة التعليمية كل هذه الأفكار في تمرين تصميم عملي.