التواصل بين الخدمات باستخدام WebClient
التواصل بين الخدمات باستخدام WebClient
بمجرد تشغيل خدمتَين مصغَّرتَين، يصبح من الحتمي أن تتحدّث إحداهما إلى الأخرى عبر HTTP. يُوفّر Spring Boot 3 عميلَين HTTP رسميَّين: الأقدم الحاجب RestTemplate (الذي يُصان حاليًا دون تطوير)، والأحدث غير الحاجب WebClient من Spring WebFlux. حتى لو كانت خدمتك مبنيةً على المكدّس التقليدي القائم على Servlet لا على المكدّس التفاعلي، يظل WebClient الخيارَ الموصى به — إذ يمكن استخدامه بأسلوب تزامني حاجب مع الاحتفاظ بقدر أكبر من الإمكانات والقابلية للاختبار مقارنةً بـ RestTemplate.
لماذا WebClient بدلًا من RestTemplate؟
جاء RestTemplate مع Spring 3.0 وهو تزامني بطبيعته: يحجب كل استدعاء الخيط المُنفِّذ حتى تستجيب الخدمة البعيدة. أعلن فريق Spring أنه في وضع الصيانة منذ Spring 5.0. في المقابل، بُني WebClient من الصفر لعالم غير حاجب، لكنه يدعم الاستدعاءات الحاجبة أيضًا، مما يجعله بديلًا مناسبًا بمجموعة ميزات أفضل:
- دعم البث (Streaming) — يمكنه استقبال
text/event-streamأو حمولات كبيرة دون تخزينها بالكامل في الذاكرة المؤقتة. - سلسلة فلاتر غنية — تتيح المعترضات (المعروفة بـ ExchangeFilterFunction) إرفاق التسجيل وترويسات المصادقة ومنطق إعادة المحاولة وقواطع الدائرة في مكان واحد.
- تفاعلي أو حاجب — استدعِ
.block()حين تحتاج نتيجةً متزامنة، واتركه تفاعليًا حين تريد I/O غير حاجب. - دعم مدمج للترميز — تسلسل JSON وفكّه تلقائيًا بواسطة Jackson دون أي شيفرة إضافية.
spring-boot-starter-webflux لاستخدام WebClient. أضف spring-boot-starter-web (Servlet) مع spring-webflux كتبعية مباشرة وستحصل على WebClient دون تحويل تطبيقك بالكامل إلى البرمجة التفاعلية.
إضافة التبعية
إذا كانت خدمتك تستخدم بالفعل spring-boot-starter-webflux، فإن WebClient موجود بالفعل في مسار الفئات. أما إذا كنت على مكدّس Servlet فأضف وحدة الويب التفاعلي فقط:
إنشاء Bean من WebClient
أنشئ دائمًا مثيلات WebClient عبر الـ Bean المُهيَّأ تلقائيًا WebClient.Builder من Spring Boot. يحمل هذا البنّاء أي ExchangeFilterFunction مسجّلة عالميًا — بما فيها تلك التي يضيفها Spring Cloud للتتبع الموزّع — لذا لا تُنشئ أبدًا WebClient.create() يدويًا في كود الإنتاج.
أعلن Bean واحدًا لكل خدمة في اتجاه المصب. استخدام Bean مخصَّص ومُسمَّى بدلًا من واحد مشترك يُبقي عناوين URL الأساسية والترويسات الافتراضية والفلاتر محدودةَ النطاق للخدمة الصحيحة.
تنفيذ طلب GET
يوضّح المثال التالي كيف تستدعي خدمة order-service خدمةَ inventory-service للتحقق من المخزون لمعرّف منتج معيّن.
شرح السلسلة الطلاقية:
.get()— يبدأ مواصفة طلب GET..uri(...)— يُلحق المسار؛ تُوسَّع متغيرات قالب URI بأمان (دون ربط سلاسل، دون خطر حقن)..retrieve()— يُطلق الطلب ويتيح الوصول إلى جسم الاستجابة. يرمي تلقائيًاWebClientResponseExceptionلرموز الحالة 4xx/5xx..bodyToMono(StockResponse.class)— يُفكك جسم JSON إلى DTO الخاص بك باستخدام Jackson..block()— يحجب الخيط المُنفِّذ حتى وصول الاستجابة. مقبول في خدمة مبنية على Servlet؛ تجنّبه في خدمة تفاعلية.
تنفيذ طلب POST مع جسم
.bodyValue() لكائن واحد و.body(BodyInserters.fromValue(...)) حين تحتاج مزيدًا من التحكم (مثل النماذج متعددة الأجزاء). كلاهما يُسلسل بنفس ObjectMapper المُهيَّأ عالميًا في تطبيقك.
معالجة أخطاء HTTP صراحةً
افتراضيًا يُعيّن .retrieve() ردود 4xx إلى WebClientResponseException.BadRequest وردود 5xx إلى WebClientResponseException.InternalServerError. يمكنك اعتراض رموز حالة بعينها وترجمتها إلى استثناءات نطاقية:
إضافة ترويسات المصادقة
في بيئة خدمات مصغَّرة حقيقية، كثيرًا ما تحمل استدعاءات الخدمة-إلى-الخدمة JWT أو مفتاح API داخلي. طبّقه عبر ExchangeFilterFunction حتى يتضمّنه كل طلب من هذا العميل تلقائيًا — دون تكرار بُنية لكل استدعاء:
المهل الزمنية — شبكة أمان أساسية
دون مهل زمنية، ستُبقي خدمة مصب بطيئة خيطَك (أو اشتراكك) معلّقًا إلى أجل غير مسمى، مما يستنزف تجمعات الاتصال في نهاية المطاف ويُولّد انهيارًا متسلسلًا على مستوى النظام. اضبط دائمًا مهلةً للاتصال وأخرى للقراءة:
قاعدة إبهام شائعة في الإنتاج: اضبط مهلة الاتصال قصيرة (1 إلى 3 ثوانٍ) ومهلة الاستجابة بما لا يتجاوز اتفاقية مستوى الخدمة التي تريد ضمانها لمُستدعييك. إذا وعدت خدمة المخزون بزمن استجابة p99 مدته ثلاث ثوانٍ، فيجب أن تكون مهلتك 4 إلى 5 ثوانٍ لتمنحها هامشًا مع الفشل السريع في الوقت ذاته.
الخلاصة
WebClient هو الأداة المعيارية للتواصل بين الخدمات عبر HTTP في Spring Boot 3. احصل عليه عبر الـ WebClient.Builder المُهيَّأ تلقائيًا، وخصّص Bean واحدًا لكل خدمة في اتجاه المصب، واستخدم قوالب URI لتجنب مخاطر الحقن، وترجم رموز أخطاء HTTP إلى استثناءات نطاقية بـ .onStatus()، وأرفق رموز المصادقة عبر ExchangeFilterFunction، واضبط دائمًا مهلتَي اتصال واستجابة صريحتَين. في الدرس القادم ستتعلم كيف يتيح لك OpenFeign التعبير عن الاستدعاءات ذاتها كواجهة تعريفية بشيفرة أقل بكثير.