اكتشاف الخدمات والإعداد والبوّابة

مشروع: إعداد خدمات مصغّرة قابلة للاكتشاف

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

مشروع: إعداد خدمات مصغّرة قابلة للاكتشاف

استعرضت الدروس التسعة السابقة كل طبقة من طبقات حزمة Spring Cloud بشكل مستقل: Eureka للتسجيل، وSpring Cloud Config للإعدادات المركزية، وSpring Cloud Gateway لنقطة الدخول الوحيدة. يجمع هذا الدرس الأخير كل هذه الطبقات في نظام عامل وقابل للنشر. في النهاية ستمتلك ثلاث خدمات قابلة للتشغيل — Config Server وسجل Eureka وAPI Gateway — بالإضافة إلى خدمة أعمال واحدة تسجّل نفسها مع Eureka وتجلب إعداداتها من Config Server ويمكن الوصول إليها عبر Gateway. كل شيء مُوصَّل بالطريقة التي يوصّلها فريق إنتاج فعليًا.

طبولوجيا النظام

قبل كتابة أي كود، تصوّر العمليات الأربع وترتيب بدء تشغيلها:

  1. Config Server — يبدأ أولًا؛ يقرأ الخصائص من مستودع Git (أو مجلد classpath محلي لأغراض المشروع). جميع الخدمات الأخرى هي عملاؤه.
  2. Eureka Server — يبدأ ثانيًا؛ يجلب إعداداته الخاصة من Config Server، ثم يفتح سجله للعمل. تسجّل الخدمات الأخرى نفسها هنا.
  3. API Gateway — يبدأ ثالثًا؛ يسجّل نفسه مع Eureka ويجلب قواعد التوجيه من Config Server. يحل عناوين الخدمات عبر Eureka عند معالجة الطلبات.
  4. Order Service (خدمة أعمال نموذجية) — يبدأ أخيرًا؛ يسجّل نفسه مع Eureka ويجلب ملف application.properties الخاص به من Config Server. يوجّه Gateway حركة المرور الواردة إليه باستخدام معرّف خدمة Eureka، وليس بعنوان مضيف أو منفذ مكتوب بشكل ثابت.
لماذا يهم ترتيب البدء: إذا لم يكن Config Server جاهزًا عندما يحاول Eureka الإقلاع، سيفشل Eureka في البدء. وإذا لم يكن Eureka جاهزًا عند بدء Gateway، سيكون لدى Gateway سجل فارغ. في بيئة الإنتاج يُطبَّق هذا الترتيب عبر فحوصات صحة تنسيق الحاويات (Kubernetes readinessProbe) أو عبر آلية إعادة المحاولة عند الاتصال في Spring Cloud. للتشغيل المحلي، ابدأ الخدمات الأربع بالترتيب المذكور مع فاصل زمني بسيط بينها.

١. Config Server

أنشئ مشروع Spring Boot مع تبعية spring-cloud-config-server، ثم زيّن الفئة الرئيسية بالتعليق المناسب:

@SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }

ملف src/main/resources/application.yml — أشر إليه نحو مجلد classpath محلي حتى يعمل المشروع بدون خادم Git بعيد:

server: port: 8888 spring: application: name: config-server cloud: config: server: native: search-locations: classpath:/config-repo profiles: active: native

أنشئ المجلد src/main/resources/config-repo/ وأضف ملفًا واحدًا لكل خدمة مصب. الصيغة المتبعة هي {application-name}.yml.

eureka-server.yml

server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false

api-gateway.yml

server: port: 8080 spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true

order-service.yml

server: port: 8081 orders: max-per-customer: 10
مستودع إعدادات واحد لخدمات متعددة: يحل Config Server الملف الصحيح بمطابقة spring.application.name للعميل المتصل. لا تكتب أبدًا بشكل ثابت أي خدمة تحصل على أي ملف — تعرّف الخدمة عن نفسها والخادم يقوم بالمطابقة.

٢. Eureka Server

أنشئ مشروعًا ثانيًا بتبعية spring-cloud-starter-netflix-eureka-server، وزيّن الفئة الرئيسية بـ @EnableEurekaServer، واضبط إقلاعه ليسحب من Config Server:

# src/main/resources/application.yml spring: application: name: eureka-server config: import: "configserver:http://localhost:8888"

هذا السطر الواحد spring.config.import هو كل ما تحتاجه للإقلاع. يعترض Spring Cloud Config Client (الموجود في classpath) عملية البدء، ويتصل بـ http://localhost:8888/eureka-server/default، ويدمج الخصائص البعيدة قبل إنشاء أي Bean. ملف eureka-server.yml الذي وضعته في مستودع الإعدادات يتحكم الآن في المنفذ 8761 وعلامات السجل المستقل.

٣. API Gateway

أنشئ مشروعًا بتبعيات spring-cloud-starter-gateway وspring-cloud-starter-netflix-eureka-client وspring-cloud-starter-config. الفئة الرئيسية:

@SpringBootApplication public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }

خصائص الإقلاع:

# src/main/resources/application.yml spring: application: name: api-gateway config: import: "configserver:http://localhost:8888" eureka: client: service-url: defaultZone: http://localhost:8761/eureka/

مع الإعداد discovery.locator.enabled: true (المضبوط في الملف البعيد api-gateway.yml)، ينشئ Gateway تلقائيًا مسارًا لكل خدمة مسجلة في Eureka. طلب على http://localhost:8080/order-service/api/orders يُحل بحذف بادئة معرّف الخدمة وإعادة التوجيه إلى أي نسخة من order-service يعرفها Eureka — دون الحاجة إلى تعريف مسارات يدويًا.

موازنة الحمل تلقائية: يستخدم Spring Cloud Gateway الفئة ReactorLoadBalancerExchangeFilterFunction المدعومة بـ Spring Cloud LoadBalancer. عندما يعيد Eureka نسخًا متعددة من order-service، يختار Gateway واحدة باستخدام استراتيجية round-robin افتراضيًا. لا Ribbon ولا كود إضافي.

٤. Order Service

خدمة REST بسيطة تقرأ قيمة إعداد وتسجّل نفسها مع Eureka:

@SpringBootApplication public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
@RestController @RequestMapping("/api/orders") @RefreshScope public class OrderController { @Value("${orders.max-per-customer:5}") private int maxPerCustomer; @GetMapping("/config") public Map<String, Object> config() { return Map.of("maxPerCustomer", maxPerCustomer); } @GetMapping public List<String> listOrders() { return List.of("ORD-001", "ORD-002"); } }
# src/main/resources/application.yml spring: application: name: order-service config: import: "configserver:http://localhost:8888" eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true

يخبر الإعداد prefer-ip-address: true سجل Eureka بتسجيل عنوان IP للخدمة بدلًا من اسم المضيف، مما يتجنب مشاكل تحليل DNS داخل الحاويات وهو الإعداد المعياري للنشر في بيئات الحاويات.

اختبار شامل للتدخين

ابدأ العمليات الأربع بالترتيب، ثم تحقق من كل طبقة:

  1. صحة Config Server: GET http://localhost:8888/order-service/default — يجب أن يعيد JSON للخصائص المدمجة.
  2. لوحة تحكم Eureka: افتح http://localhost:8761 — يجب أن ترى API-GATEWAY وORDER-SERVICE مدرجَين بحالة UP.
  3. استدعاء مباشر: GET http://localhost:8081/api/orders — يتجاوز Gateway ويؤكد عمل الخدمة.
  4. استدعاء عبر Gateway: GET http://localhost:8080/order-service/api/orders — يُوجَّه عبر Eureka ويؤكد عمل الاكتشاف والتوجيه معًا.
  5. قراءة الإعداد: GET http://localhost:8080/order-service/api/orders/config — يجب أن يعيد {"maxPerCustomer": 10} مما يثبت أن القيمة جاءت من Config Server.

اعتبارات الأمان

يحتاج الإعداد الإنتاجي إلى عدة خطوات تصليب يتعمّد هيكل المشروع هذا تجاهلها للوضوح:

  • تأمين نقاط actuator والإعدادات في Config Server. أضف Spring Security إلى Config Server واشترط بيانات اعتماد HTTP Basic أو OAuth2. بدون ذلك، يمكن لأي شخص يصل إلى المنفذ 8888 قراءة أسرار كل خدمة.
  • تشفير القيم الحساسة. يدعم Config Server التشفير المتماثل (AES) وغير المتماثل (RSA) عبر بادئات {cipher}. خزّن النص المشفّر في Git؛ يقوم الخادم بفك التشفير عند التسليم.
  • تقييد منفذ إدارة Eureka. واجهة برمجة /eureka/apps غير محمية بمصادقة افتراضيًا. في الإنتاج، ضع Eureka خلف شبكة خاصة أو أضف Spring Security بكلمة مرور لحساب الخدمة.
  • لا تعرض نقاط إدارة Gateway للعامة. تكشف نقطة النهاية /actuator/gateway/routes عن جدول التوجيه بالكامل. اربط منفذ الإدارة بواجهة غير عامة.
محدّد الاكتشاف التلقائي قوي لكنه واسع النطاق. مع الإعداد discovery.locator.enabled: true، تصبح كل خدمة في Eureka قابلة للوصول عبر Gateway. إذا سجّلت خدمة إدارة داخلية مع Eureka، فهي الآن متاحة للعامة ما لم تضف محددات مسار صريحة أو مرشّح أمان لحجبها. في الإنتاج، يُفضَّل استخدام تعريفات مسارات صريحة بدلًا من محدد الاكتشاف التلقائي، أو إدراج معرّفات الخدمات المسموح بتعريضها في قائمة بيضاء.

مقايضات الأنظمة الموزعة

تحل هذه البنية مشاكل حقيقية لكنها تُدخل مشاكل جديدة. تعرّف على المقايضات قبل الالتزام:

  • Config Server نقطة فشل منفردة. إذا توقف عند الإقلاع، لا تستطيع الخدمات التابعة البدء. تُخفَّف هذه المشكلة بتشغيل مجموعة من Config Server أو بتخزين نسخة محلية مؤقتة من الخصائص عبر spring.cloud.config.fail-fast: false مع ملف application.yml احتياطي محلي.
  • اتساق Eureka في نهاية المطاف. يستخدم Eureka نموذج نبضات القلب، وليس بروتوكول توافق. يمكن أن تبقى خدمة أُلغي تسجيلها حديثًا في السجل لمدة تصل إلى 90 ثانية (3 نبضات قلب فائتة). يجب أن يتحمّل عملاؤك عددًا صغيرًا من الاستدعاءات إلى نسخ غير نشطة — وهذا بالضبط السبب الذي يجعل قواطع الدائرة (Resilience4j، المغطى في الدرس القادم) تتكامل بشكل طبيعي مع اكتشاف الخدمات.
  • Gateway نقطة اختناق. تمر كل حركة المرور العامة عبر عملية واحدة. شغّل نسختين على الأقل من Gateway خلف موازن حمل خارجي أو سحابي، وحجّمهما لذروة التحميل على أكثر مساراتك تكلفةً.

الخلاصة

لديك الآن هيكل عظمي كامل وعامل: Config Server يمتلك جميع الخصائص، وسجل Eureka يمتلك جميع العناوين، وGateway يحل كليهما عند معالجة الطلبات. لا تعرف Order Service أبدًا أين تقع نظيراتها — إنها تسأل Eureka. ولا تمتلك إعداداتها الخاصة — إنها تسأل Config Server. إضافة خدمة جديدة لهذا النظام تعني كتابة منطق الأعمال وspring.application.name؛ والبنية التحتية تتكفّل بالباقي. هذا هو المكسب الحقيقي للأنماط التي درستها في هذا البرنامج التعليمي.