الردود وResponseEntity
الردود وResponseEntity
في الدروس السابقة تعلّمت كيف ترسم خرائط الطلبات الواردة وتربط بياناتها. الآن ينصبّ التركيز على الجانب الصادر: كيف تُترجم Spring Boot قيمة الإرجاع إلى استجابة HTTP، وكيف يمنحك ResponseEntity تحكمًا دقيقًا في كل جزء من تلك الاستجابة — رمز الحالة والترويسات والجسم.
الافتراضي: إرجاع كائن POJO ودع Spring يتولى الأمر
حين تُرجع إحدى توابع @RestController كائن Java عاديًا، تقوم بنية HttpMessageConverter في Spring تلقائيًا بتسلسله إلى JSON (أو XML). تكون حالة HTTP الافتراضية 200 OK وتُضبط ترويسة Content-Type على application/json.
هذا مناسب تمامًا للمسار السعيد، لكن ماذا حين لا يُعثر على المورد؟ أو حين تنشئ شيئًا وتحتاج إلى إرجاع 201 Created مع ترويسة Location؟ لتلك الحالات تحتاج إلى ResponseEntity.
ResponseEntity — تحكم كامل في الاستجابة
ResponseEntity<T> غلاف عام يجمع جسمًا من النوع T وحالة HttpStatus ومجموعة HttpHeaders في قيمة إرجاع واحدة. وهو جزء من org.springframework.http ويعمل في أي متحكم Spring MVC أو Spring WebFlux.
ResponseEntity<T> بدلًا من T مباشرةً؟ لأن HTTP أكثر من مجرد ناقل لـ JSON. رموز الحالة تُبلّغ العملاء والوسيط وأنظمة المراقبة بالنية. إرجاع كائن برمز 200 ثابت حين لم يُعثر على المورد يُجبر العميل على فحص الجسم لاكتشاف الفشل — وهذا عقد مُسرِّب.واجهة برمجة البناء (Builder API)
بناء ResponseEntity عبر مُنشئاته (constructors) مباشرةً مطوّل. واجهة البناء الطليقة (المتاحة منذ Spring 4) أكثر وضوحًا:
ضبط الترويسات المخصصة
تحمل الترويسات بيانات وصفية ليست جزءًا من الجسم: مؤشرات ترقيم الصفحات، ومعلومات حدود المعدل، وتوجيهات التخزين المؤقت، وغيرها. تضيفها عبر HttpHeaders أو مباشرةً على البنّاء:
ترويسة Location التي يضبطها .created(uri) تُخبر العميل بالضبط أين يجد المورد المُنشأ حديثًا. تعتمد عليها عملاء REST وأطر hypermedia وبوابات API.
التحكم في رمز الحالة صراحةً
استخدم ResponseEntity.status(HttpStatus.XXX) حين لا تُغطّي الأساليب المُختصرة الرمز الصحيح. مثلًا، إرجاع 202 Accepted لمهمة غير متزامنة:
إرجاع جسم فارغ
ينبغي لعدة عمليات HTTP أن تُرجع جسمًا فارغًا: الحذف الناجح بـ DELETE، والتحديث الناجح بـ PUT حين لا تُعيد المورد المحدّث، وإشعارات الاستلام. استخدم ResponseEntity<Void> للتعبير عن ذلك بوضوح على مستوى النوع:
ResponseEntity<Void> على ResponseEntity<?> للاستجابات ذات الجسم الفارغ. يجعل ذلك العقد واضحًا في توقيع التابع: يعرف المُستدعون وأدوات توليد الكود أنه لا يوجد جسم عن قصد، بدلًا من التساؤل عمّا إذا كانت علامة الاستفهام تُخفي شيئًا.
الاستجابات الشرطية باستخدام ETags وIf-None-Match
للنقاط الطرفية كثيفة القراءة يمكنك دعم الطلبات الشرطية لـ HTTP لتقليل النطاق الترددي. توفر Spring مرشّح ShallowEtagHeaderFilter لتوليد ETag تلقائيًا، لكن يمكنك أيضًا ضبط ترويسات ETag يدويًا عبر ResponseEntity حين تحتاج إلى تحكم دقيق:
ResponseEntity المكتوب مقابل ذي البدل
ستشاهد أحيانًا توابع تُصرّح بـ ResponseEntity<?> أو ResponseEntity<Object>. هذا في معظم الحالات رائحة كود سيئة. فهو يتخلى عن سلامة النوع وقت الترجمة، ويُربك أدوات بيئة التطوير، ويعطّل توليد مخطط OpenAPI. الاستخدام المشروع الوحيد هو حين تُرجع نقطة طرفية واحدة أنواع جسم مختلفة حقًا بحسب النتيجة — وحتى في هذه الحالة يكون معالج الاستثناءات المناسب (@ControllerAdvice) التصميم الأفضل عادةً.
ResponseEntity<?> كاختصار كسول. حين تجد نفسك تريد إرجاع إما ProductDto أو سلسلة خطأ من التابع نفسه، فهذه إشارة إلى أن حالة الخطأ ينبغي أن تكون استثناءً مُرمَيًا من الخدمة ويُترجمه صف @ControllerAdvice — لا مسار كود ثانٍ داخل التابع.
النمط العملي: يُرجع الخدمة Optional
نمط نظيف وإيديوماتيكي لنقاط طرفية GET هو أن يُرجع الخدمة Optional وتُعيّنه إلى ResponseEntity المناسبة في المتحكم:
يُبقي هذا النمط المتحكم نحيفًا، ومعالجة القيم الفارغة صريحة، ويتجنّب فرعًا شرطيًا يسهل نسيانه.
الخلاصة
تُسلسل Spring Boot قيم الإرجاع العادية إلى JSON برمز حالة افتراضي 200. يرفع ResponseEntity<T> هذا الافتراضي، مانحًا إياك تحكمًا من الدرجة الأولى في رمز الحالة والترويسات والجسم في كائن واحد آمن النوع. استخدم واجهة البناء الطليقة — ResponseEntity.ok() و.created(uri) و.noContent() و.status(HttpStatus.X) — لكود واضح يُعبّر عن النية. اكتب نوع المعامل في ResponseEntity بنوع محدد؛ والجأ إلى <Void> حين يكون الجسم فارغًا عن قصد. في الدرس القادم ستعمّق فهمك لرموز حالة HTTP نفسها ودلالات REST التي تحملها.