404 Not Found

404 Not Found


nginx

دليل البدء السريع لواجهة REST API

واجهات REST API هي اللغة العالمية للخدمات المصغرة—تستخدم أفعال HTTP للتلاعب بالموارد وJSON لنقل البيانات، مما يجعلها بسيطة وموحدة وفعالة.

1. ما ستتعلمه


2. قصة حقيقية من مطوّر واجهات

(1) نقطة الألم: مواصفات API غير متسقة

انضمت Alice إلى فريق مشروع قائم واكتشفت أن تصميم REST API كان فوضوياً: بعض الطرق تستخدم GET لتعديل البيانات، وتسمية عناوين URL غير متسقة (/getOrder، /order/list، /deleteOrderById)، وتنسيقات الاستجابة غير موحدة—بعضها يُرجع JSON، وبعضها يُرجع XML، واستجابات الأخطاء نص عادي. كان Bob، مطور الواجهة الأمامية، يضطر لاستشارة فريق الواجهة الخلفية في كل مرة يدمج واجهة API جديدة، مما يضيع قدراً كبيراً من الوقت في التواصل.

(2) النهج RESTful

REST يستخدم مجموعة من الاصطلاحات الموحدة لتعريف تصميم API:

JAVA
@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 (نقل حالة التمثيل) يحدد القيود الأساسية لنمط البنية هذا:

100%
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
📌 نقطة رئيسية: التساوي (Idempotence) يعني أن تنفيذ العملية عدة مرات يُعطي نفس النتيجة. PUT متساوٍ (يستبدل المورد بالكامل)، بينما POST غير متساوٍ (قد ينشئ مورداً جديداً في كل مرة).


4. @RestController وتعيين الطلبات

(1) كيف يعمل @RestController

@RestController = @Controller + @ResponseBody يشير إلى أن قيم الإرجاع لجميع الطرق في هذا الصنف تُكتب مباشرة في محتوى استجابة HTTP (JSON بشكل افتراضي).

(1) ▶ مثال: OrderController أساسي

JAVA
@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;
    }
}

الناتج:

TEXT
// التنفيذ ناجح

(2) ربط معاملات الطلب

(2) ▶ مثال: @PathVariable و@RequestParam

JAVA
@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
        ));
    }
}

الناتج:

TEXT
// التنفيذ ناجح
التعليق الغرض مثال 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 لاستقبال محتوى الطلب

JAVA
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);
    }
}

الناتج:

TEXT
// التنفيذ ناجح

5. ResponseEntity ورموز حالة HTTP

(1) استخدام ResponseEntity

ResponseEntity يمنحك تحكماً كاملاً في استجابات HTTP: رموز الحالة، وترويسات الاستجابة، ومحتوى الاستجابة.

الطريقة السيناريوهات المناسبة المرونة
إرجاع كائن مباشرة استجابة نجاح بسيطة منخفضة (ثابتة عند 200)
ResponseEntity.ok(body) يجب تعيينها إلى 200 متوسطة
ResponseEntity.status(CREATED).body(body) إنشاء مورد يُرجع 201 عالية
ResponseEntity.notFound().build() المورد غير موجود—يُرجع 404 عالية

(1) ▶ مثال: عمليات CRUD كاملة

JAVA
@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();
    }
}

الناتج:

TEXT
// التنفيذ ناجح

6. طرق اختبار API

(1) أوامر اختبار curl

(1) ▶ مثال: اختبار واجهات CRUD API باستخدام curl

BASH
# إنشاء طلب
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

الناتج:

TEXT
{"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

JAVA
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();
    }
}

❓ أسئلة شائعة

س ما الفرق بين @Controller و@RestController؟
ج @Controller يُرجع اسم عرض (لاستخدامه مع محرك قوالب)، بينما @RestController = @Controller + @ResponseBody، الذي يُسلسل قيمة الإرجاع مباشرة إلى JSON. استخدم دائماً @RestController عند كتابة واجهات REST API.
س ما الفرق بين PUT وPATCH؟
ج PUT يقوم بالاستبدال الكامل، لذا يجب تمرير الكائن بالكامل؛ PATCH يقوم بالتحديث الجزئي، لذا تحتاج فقط لتمرير الحقول التي تحتاج تعديل. Spring Boot يدعم كليهما، لكن PATCH يتطلب منطق دمج مخصص.
س متى أستخدم @PathVariable ومتى أستخدم @RequestParam؟
ج @PathVariable يُستخدم لتحديد المُعرّف الفريد للمورد (مثل مُعرّف الطلب)، بينما @RequestParam يُستخدم لمعاملات التصفية والترقيم (مثل الحالة والصفحة). قاعدة بسيطة: استخدم @PathVariable للمسارات و@RequestParam لمعاملات الاستعلام.
س هل يجب أن أُرجع ResponseEntity أم أُرجع الكائن مباشرة؟
ج للاستعلامات البسيطة، يمكنك إرجاع الكائن مباشرة (الذي يُرجع رمز حالة 200 تلقائياً). استخدم ResponseEntity عندما تحتاج لتعيين رموز حالة مخصصة (مثل 201 أو 404) أو ترويسات استجابة.
س كيف يجب التعامل مع تنسيقات التاريخ والوقت؟
ج بشكل افتراضي، تُسلسل كطوابع زمنية. يمكنك تهيئة ذلك في application.yml أو استخدام @JsonFormat(pattern = "yyyy-MM-dd") على الحقل.
س هل تتطلب واجهة REST API التحكم في الإصدارات؟
ج نوصي بالتحكم في الإصدارات المستند إلى URL (/api/v1/orders)، وهو بسيط وبديهي. يمكنك أيضاً استخدام التحكم في الإصدارات المستند إلى الترويسات (Accept: application/vnd.orderflow.v1+json)، وهو أكثر توافقاً مع REST لكنه أكثر تعقيداً في التنفيذ.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة ⭐): نفّذ واجهات CRUD API لمورد Product في OrderFlow، بما في ذلك GET /api/v1/products، POST /api/v1/products، GET /api/v1/products/{id}، وDELETE /api/v1/products/{id}.

  2. مسألة متقدمة (الصعوبة: ⭐⭐): أضف نقطة النهاية PATCH /api/v1/orders/{id}/status إلى واجهة Order API التي تُحدّث حقل حالة الطلب فقط. فكّر في الفرق بين PATCH وPUT.

  3. تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ واجهة المورد المتداخل GET /api/v1/orders/{id}/items لإرجاع جميع عناصر الطلب تحت طلب محدد، مع دعم معاملات ترقيم الصفحات.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%