دليل البدء السريع لواجهة REST API
واجهات REST API هي اللغة العالمية للخدمات المصغرة—تستخدم أفعال HTTP للتلاعب بالموارد وJSON لنقل البيانات، مما يجعلها بسيطة وموحدة وفعالة.
1. ما ستتعلمه
- نظام التعليقات
@RestController/@RequestMapping/@GetMapping/@PostMapping - متغير المسار
@PathVariable، معامل الطلب@RequestParam، محتوى الطلب@RequestBody - رموز حالة HTTP واستجابات
ResponseEntityالمخصصة - اختبار واجهات REST API باستخدام Postman / curl
- Alice تُنفذ العمليات الأربع للطلبات: الإنشاء، الاستعلام، التحديث، والحذف
2. قصة حقيقية من مطوّر واجهات
(1) نقطة الألم: مواصفات API غير متسقة
انضمت Alice إلى فريق مشروع قائم واكتشفت أن تصميم REST API كان فوضوياً: بعض الطرق تستخدم GET لتعديل البيانات، وتسمية عناوين URL غير متسقة (/getOrder، /order/list، /deleteOrderById)، وتنسيقات الاستجابة غير موحدة—بعضها يُرجع JSON، وبعضها يُرجع XML، واستجابات الأخطاء نص عادي. كان Bob، مطور الواجهة الأمامية، يضطر لاستشارة فريق الواجهة الخلفية في كل مرة يدمج واجهة API جديدة، مما يضيع قدراً كبيراً من الوقت في التواصل.
(2) النهج RESTful
REST يستخدم مجموعة من الاصطلاحات الموحدة لتعريف تصميم API:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}") // GET /api/orders/123
@PostMapping // POST /api/orders
@PutMapping("/{id}") // PUT /api/orders/123
@DeleteMapping("/{id}") // DELETE /api/orders/123
}
(3) العوائد
بعد أن أعادت Alice بناء OrderFlow API بأسلوب RESTful—باصطلاحات تسمية URL متسقة ودلالات أفعال HTTP محددة بوضوح—قال Bob، مطور الواجهة الأمامية: "يمكنك معرفة كيفية استخدامه بمجرد النظر إلى URL." ونتيجة لذلك، انخفض الوقت المطلوب لدمج واجهة API من متوسط ساعتين إلى 15 دقيقة.
3. المفاهيم الأساسية لـ REST
(1) المبادئ الستة لـ REST
REST (نقل حالة التمثيل) يحدد القيود الأساسية لنمط البنية هذا:
graph TD
A[قيود REST] --> B[Client-Server<br/>فصل المسؤوليات]
A --> C[Stateless<br/>كل طلب يحتوي على<br/>كل المعلومات المطلوبة]
A --> D[Cacheable<br/>الاستجابات تحدد<br/>قابلية التخزين المؤقت]
A --> E[Uniform Interface<br/>تصميم API متسق]
A --> F[Layered System<br/>العميل لا يستطيع التمييز<br/>إذا كان متصلاً مباشرة]
A --> G[Code on Demand<br/>اختياري: الخادم يمكنه<br/>إرسال كود قابل للتنفيذ]
(2) تعيين أفعال HTTP لعمليات CRUD
| فعل HTTP | العملية | التساوي | الأمان | URL النموذجي |
|---|---|---|---|---|
| GET | استعلام | نعم | نعم | /api/orders |
| POST | إنشاء | لا | لا | /api/orders |
| PUT | تحديث كامل | نعم | لا | /api/orders/123 |
| PATCH | تحديث جزئي | لا | لا | /api/orders/123 |
| DELETE | حذف | نعم | لا | /api/orders/123 |
4. @RestController وتعيين الطلبات
(1) كيف يعمل @RestController
@RestController = @Controller + @ResponseBody يشير إلى أن قيم الإرجاع لجميع الطرق في هذا الصنف تُكتب مباشرة في محتوى استجابة HTTP (JSON بشكل افتراضي).
(1) ▶ مثال: OrderController أساسي
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final List<Map<String, Object>> orders = new ArrayList<>();
@GetMapping
public List<Map<String, Object>> listOrders() {
return orders;
}
@PostMapping
public Map<String, Object> createOrder(
@RequestBody Map<String, Object> order) {
order.put("id", (long) (orders.size() + 1));
order.put("status", "PENDING");
orders.add(order);
return order;
}
}
الناتج:
// التنفيذ ناجح
(2) ربط معاملات الطلب
(2) ▶ مثال: @PathVariable و@RequestParam
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public Map<String, Object> getOrder(@PathVariable Long id) {
return Map.of("id", id, "status", "PENDING");
}
@GetMapping
public List<Map<String, Object>> searchOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status) {
return List.of(Map.of(
"page", page, "size", size, "status", status
));
}
}
الناتج:
// التنفيذ ناجح
| التعليق | الغرض | مثال | URL |
|---|---|---|---|
@PathVariable |
استخراج متغيرات من المسار | @PathVariable Long id |
/api/orders/123 |
@RequestParam |
استخراج معاملات الاستعلام | @RequestParam String status |
/api/orders?status=PENDING |
@RequestBody |
استخراج JSON من محتوى الطلب | @RequestBody OrderRequest req |
محتوى POST |
@RequestHeader |
استخراج ترويسات الطلب | @RequestHeader String auth |
ترويسة: Authorization |
(3) ▶ مثال: استخدام DTO لاستقبال محتوى الطلب
public record CreateOrderRequest(
Long productId,
Integer quantity,
String shippingAddress
) {}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public ResponseEntity<Map<String, Object>> createOrder(
@RequestBody CreateOrderRequest request) {
Map<String, Object> order = Map.of(
"productId", request.productId(),
"quantity", request.quantity(),
"shippingAddress", request.shippingAddress(),
"status", "PENDING"
);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(order);
}
}
الناتج:
// التنفيذ ناجح
5. ResponseEntity ورموز حالة HTTP
(1) استخدام ResponseEntity
ResponseEntity يمنحك تحكماً كاملاً في استجابات HTTP: رموز الحالة، وترويسات الاستجابة، ومحتوى الاستجابة.
| الطريقة | السيناريوهات المناسبة | المرونة |
|---|---|---|
| إرجاع كائن مباشرة | استجابة نجاح بسيطة | منخفضة (ثابتة عند 200) |
ResponseEntity.ok(body) |
يجب تعيينها إلى 200 | متوسطة |
ResponseEntity.status(CREATED).body(body) |
إنشاء مورد يُرجع 201 | عالية |
ResponseEntity.notFound().build() |
المورد غير موجود—يُرجع 404 | عالية |
(1) ▶ مثال: عمليات CRUD كاملة
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> orderStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest request) {
Long id = idGenerator.getAndIncrement();
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "PENDING");
orderStore.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getOne(@PathVariable Long id) {
Map<String, Object> order = orderStore.get(id);
if (order == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(order);
}
@PutMapping("/{id}")
public ResponseEntity<Map<String, Object>> update(
@PathVariable Long id,
@RequestBody CreateOrderRequest request) {
if (!orderStore.containsKey(id)) {
return ResponseEntity.notFound().build();
}
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "CONFIRMED");
orderStore.put(id, order);
return ResponseEntity.ok(order);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if (orderStore.remove(id) == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build();
}
}
الناتج:
// التنفيذ ناجح
6. طرق اختبار API
(1) أوامر اختبار curl
(1) ▶ مثال: اختبار واجهات CRUD API باستخدام curl
# إنشاء طلب
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"productId":1, "quantity":3, "shippingAddress":"123 Main St"}'
# الحصول على طلب
curl http://localhost:8080/api/orders/1
# عرض الطلبات مع ترقيم الصفحات
curl "http://localhost:8080/api/orders?page=0&size=10"
# تحديث طلب
curl -X PUT http://localhost:8080/api/orders/1 \
-H "Content-Type: application/json" \
-d '{"productId":2, "quantity":5, "shippingAddress":"456 Oak Ave"}'
# حذف طلب
curl -X DELETE http://localhost:8080/api/orders/1
الناتج:
{"status":"ok","data":{}}
| رمز حالة HTTP | المعنى | متى يُرجع |
|---|---|---|
| 200 OK | نجاح | نجاح GET / PUT |
| 201 Created | تم الإنشاء | تم إنشاء مورد POST بنجاح |
| 204 No Content | بدون محتوى | نجاح DELETE |
| 400 Bad Request | خطأ في الطلب | فشل التحقق من المعاملات |
| 404 Not Found | غير موجود | المورد غير موجود |
7. مواصفات تصميم واجهة RESTful API
(1) اصطلاحات تسمية URL
| القاعدة | الشكل الخاطئ | الشكل الصحيح |
|---|---|---|
| استخدام الأسماء الجمع | /getOrder |
/api/orders |
| علاقات الموارد المتداخلة | /orderItems?orderId=1 |
/api/orders/1/items |
| استخدام المسارات بدلاً من الاستعلامات | /api/orders?id=1 |
/api/orders/1 |
| التحكم في الإصدارات | بدون | /api/v1/orders |
| معاملات الاستعلام للتصفية | /api/pendingOrders |
/api/orders?status=PENDING |
8. مثال شامل: واجهة CRUD لطلبات OrderFlow
package com.orderflow.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public record CreateOrderRequest(
Long productId, Integer quantity, String shippingAddress
) {}
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest req) {
Long id = seq.getAndIncrement();
Map<String, Object> order = new LinkedHashMap<>();
order.put("id", id);
order.put("productId", req.productId());
order.put("quantity", req.quantity());
order.put("shippingAddress", req.shippingAddress());
order.put("status", "PENDING");
order.put("createdAt", java.time.Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping
public List<Map<String, Object>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return store.values().stream()
.skip((long) page * size).limit(size).toList();
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> get(@PathVariable Long id) {
Map<String, Object> order = store.get(id);
return order != null ? ResponseEntity.ok(order)
: ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return store.remove(id) != null
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
}
❓ أسئلة شائعة
ResponseEntity أم أُرجع الكائن مباشرة؟ResponseEntity عندما تحتاج لتعيين رموز حالة مخصصة (مثل 201 أو 404) أو ترويسات استجابة.application.yml أو استخدام @JsonFormat(pattern = "yyyy-MM-dd") على الحقل./api/v1/orders)، وهو بسيط وبديهي. يمكنك أيضاً استخدام التحكم في الإصدارات المستند إلى الترويسات (Accept: application/vnd.orderflow.v1+json)، وهو أكثر توافقاً مع REST لكنه أكثر تعقيداً في التنفيذ.📖 ملخص
- REST يربط عمليات CRUD بأفعال HTTP: GET للاسترجاع، POST للإنشاء، PUT للتحديث، DELETE للحذف
@RestController=@Controller+@ResponseBody، يُرجع JSON@PathVariableيسترجع متغير المسار،@RequestParamيسترجع معاملات الاستعلام،@RequestBodyيسترجع محتوى الطلبResponseEntityتحكم كامل في رموز حالة الاستجابة والترويسات والمحتوى- عناوين URL بأسلوب RESTful تستخدم أسماء جمع؛ التداخل يدل على العلاقات بين الموارد؛ معاملات الاستعلام تُستخدم للتصفية
📝 تمارين
-
تمرين أساسي (الصعوبة ⭐): نفّذ واجهات CRUD API لمورد Product في OrderFlow، بما في ذلك
GET /api/v1/products،POST /api/v1/products،GET /api/v1/products/{id}، وDELETE /api/v1/products/{id}. -
مسألة متقدمة (الصعوبة: ⭐⭐): أضف نقطة النهاية
PATCH /api/v1/orders/{id}/statusإلى واجهة Order API التي تُحدّث حقل حالة الطلب فقط. فكّر في الفرق بين PATCH وPUT. -
تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ واجهة المورد المتداخل
GET /api/v1/orders/{id}/itemsلإرجاع جميع عناصر الطلب تحت طلب محدد، مع دعم معاملات ترقيم الصفحات.



