أجسام الطلبات و @RequestBody
أجسام الطلبات و @RequestBody
عندما يُرسل العميل بيانات إلى واجهة برمجية — لإنشاء مستخدم جديد، أو تحديث طلب، أو إرسال نموذج — تنتقل هذه البيانات داخل جسم طلب HTTP، وعادةً ما تكون بتنسيق JSON. تُخبر الحاشية @RequestBody في Spring Boot الإطارَ بأن يحوّل ذلك JSON تلقائيًا إلى كائن Java. إنّ فهم آلية هذه العملية، وسبب ربط البيانات بـ كائن نقل البيانات (DTO) بدلًا من كيان JPA مباشرةً، يُعدّ من أهم القرارات التصميمية التي ستتخذها في أي REST API.
كيف تعمل @RequestBody
عندما يصل طلب إلى دالة في المتحكّم (controller) المحدودة بـ @RequestBody، يُفوّض DispatcherServlet في Spring المهمةَ إلى HttpMessageConverter. نظرًا لأن Spring Boot يُهيّئ Jackson تلقائيًا (عبر MappingJackson2HttpMessageConverter)، فإن كل دالة في المتحكّم تقبل طلبًا بـ Content-Type: application/json تحصل مجانًا على إمكانية تحويل JSON إلى Java.
تُنشئ Spring نسخةً من الفئة المستهدفة، وتطابق اسم كل حقل JSON مع حقل Java أو setter باسمه، ثم تُعبّئه. أما الحقول الغائبة في JSON فتبقى بقيمها الافتراضية (عادةً null أو صفر).
null. هذا خطأ شائع جدًا للمبتدئين.
ما هو DTO ولماذا يهم؟
كائن نقل البيانات (DTO) هو فئة Java بسيطة تهدف فقط إلى نقل البيانات عبر حدود — في هذه الحالة بين طبقة HTTP وطبقة الخدمة. لا يحتوي على حاشيات persistence، ولا منطق أعمال، ولا تبعيات للإطار. قارن بين المقاربتين:
المقاربة الأولى — الربط مباشرةً بكيان JPA (تجنّب هذا):
المقاربة الثانية — الربط بـ DTO مخصص (المقاربة الصحيحة):
تعاني المقاربة الأولى من مشكلة الإسناد الجماعي (mass assignment) — يستطيع عميل خبيث تعيين حقول مثل id أو role أو isAdmin بمجرد تضمينها في جسم JSON. ستُسعدها Spring في الربط ما لم تحجب كل حقل يدويًا. أما المقاربة الثانية فآمنة افتراضيًا: لا يستطيع العميل سوى توفير الحقول التي وضعتها عمدًا في DTO.
تعريف فئة DTO
DTO هو POJO عادي. لطلب إنشاء مستخدم يمكن أن يبدو هكذا:
لاحظ حاشيات jakarta.validation.constraints.*. لا تفعل شيئًا بمفردها — يجب تفعيلها بـ @Valid في المتحكّم.
التحقق من صحة جسم الطلب بـ @Valid
أضف @Valid (من jakarta.validation) مباشرةً قبل معامل @RequestBody. ستُشغّل Spring Boot Bean Validation على DTO قبل تنفيذ جسم الدالة. إذا فشل أي قيد، تُعيد Spring تلقائيًا 400 Bad Request.
@Valid (Jakarta EE) Bean Validation المعيارية على معامل الدالة. أما @Validated (Spring) فتدعم إضافةً مجموعات التحقق. بالنسبة لمعظم نقاط نهاية REST، @Valid هي الخيار الصحيح — احرص على البساطة.
استخدام Java Records كـ DTOs في Spring Boot 3+
Records في Java غير قابلة للتغيير وموجزة ومثالية للـ DTOs. يُحوّلها Jackson 2.12+ من JSON تلقائيًا دون أي إعداد خاص في Spring Boot 3:
يحلّ Record محل كل الكود المتكرر من getters وconstructor وequals/hashCode بالكامل. استخدم records للـ DTOs الخاصة بالطلبات؛ تفرض عدم القابلية للتغيير فلا يمكن لأي شيء تغيير البيانات الواردة بالصدفة.
الفصل بين DTOs للطلب والاستجابة
النمط الشائع هو استخدام فئات منفصلة للبيانات الواردة والصادرة:
- DTO الطلب (
UserCreateRequest) — الحقول المسموح للعميل بإرسالها. يحمل حاشيات التحقق. - DTO الاستجابة (
UserResponse) — الحقول المسموح للعميل برؤيتها. يحذف البيانات الحساسة ككلمات المرور والأعلام الداخلية.
تُنشئ طبقة الخدمة الكيان من DTO الطلب، تحفظه، وتعيد DTO الاستجابة المبني من الكيان المحفوظ. لا يرى العميل فئة الكيان مطلقًا.
التعامل مع الأجسام المتداخلة والقوائم
يتعامل Jackson مع الكائنات المتداخلة والقوائم تلقائيًا. إذا احتوى JSON الخاص بك على مصفوفة في المستوى الأعلى، اعلن المعامل كـ List<YourDto>:
@Valid على معامل List وحده لا تُطبّق التحقق على العناصر الداخلية في بعض إعدادات Spring. ضع الحاشية على معامل النوع الجنيسي (List<@Valid UserCreateRequest>) أو تحقق يدويًا في طبقة الخدمة.
الخلاصة
تُخبر @RequestBody Spring بتحويل جسم JSON إلى كائن Java باستخدام Jackson. ارتبط دائمًا بـ DTO مخصص — وليس بكيان JPA — للحماية من هجمات الإسناد الجماعي وفصل سطح API عن مخطط قاعدة البيانات. أضف @Valid لتفعيل Bean Validation قبل تشغيل الدالة. تُعدّ Java Records أكثر صيغة DTO إيجازًا وأمانًا في Spring Boot 3. استخدم DTOs منفصلة للطلب والاستجابة لتتحكم بدقة فيما يستطيع العميل إرساله وما يستلمه. هذا النمط هو الأساس الذي يُبنى عليه كل REST API احترافي في Spring Boot.