كيفية تنفيذ الترقيم في واجهات برمجة تطبيقات REST: دليل خطوة بخطوة

Mark Ponomarev

Mark Ponomarev

19 مايو 2025

كيفية تنفيذ الترقيم في واجهات برمجة تطبيقات REST: دليل خطوة بخطوة

عند بناء واجهات برمجة تطبيقات REST (REST APIs) التي تُرجع قائمة بالموارد، من الضروري النظر في كيفية التعامل مع مجموعات البيانات الكبيرة. إن إرجاع آلاف أو حتى ملايين السجلات في استجابة واحدة لواجهة برمجة التطبيقات أمر غير عملي ويمكن أن يؤدي إلى مشاكل كبيرة في الأداء، واستهلاك عالٍ للذاكرة لكل من الخادم والعميل، وتجربة مستخدم سيئة. الترقيم (Pagination) هو الحل القياسي لهذه المشكلة. يتضمن تقسيم مجموعة بيانات كبيرة إلى أجزاء أصغر قابلة للإدارة تسمى "صفحات"، والتي يتم تقديمها بعد ذلك بالتتابع. سيرشدك هذا البرنامج التعليمي خلال الخطوات التقنية لتنفيذ استراتيجيات ترقيم متنوعة في واجهات برمجة تطبيقات REST الخاصة بك.

💡
هل تريد أداة رائعة لاختبار واجهات برمجة التطبيقات تولد توثيقًا جميلًا لواجهات برمجة التطبيقات؟

هل تريد منصة متكاملة وشاملة لفريق المطورين الخاص بك للعمل معًا بأقصى إنتاجية؟

يلبي Apidog جميع متطلباتك، ويحل محل Postman بسعر معقول جدًا!
زر

لماذا الترقيم ضروري؟

قبل الخوض في تفاصيل التنفيذ، دعونا نتطرق بإيجاز إلى سبب كون الترقيم ميزة غير قابلة للتفاوض لواجهات برمجة التطبيقات التي تتعامل مع مجموعات الموارد:

  1. الأداء: قد يكون طلب ونقل كميات كبيرة من البيانات بطيئًا. يقلل الترقيم من حجم حمولة كل طلب، مما يؤدي إلى أوقات استجابة أسرع وتقليل حمل الخادم.
  2. استهلاك الموارد: الاستجابات الأصغر تستهلك ذاكرة أقل على الخادم الذي يولدها وعلى العميل الذي يحللها. هذا أمر بالغ الأهمية بشكل خاص للعملاء المتنقلين أو البيئات ذات الموارد المحدودة.
  3. تحديد المعدل والحصص: تفرض العديد من واجهات برمجة التطبيقات تحديدًا للمعدل. يساعد الترقيم العملاء على البقاء ضمن هذه الحدود عن طريق جلب البيانات في أجزاء أصغر بمرور الوقت، بدلاً من محاولة الحصول على كل شيء دفعة واحدة.
  4. تجربة المستخدم: بالنسبة لواجهات المستخدم التي تستهلك واجهة برمجة التطبيقات، فإن تقديم البيانات في صفحات يكون أكثر سهولة في الاستخدام من إغراق المستخدمين بقائمة ضخمة أو تمرير طويل جدًا.
  5. كفاءة قاعدة البيانات: جلب مجموعة فرعية من البيانات يكون عمومًا أقل إرهاقًا لقاعدة البيانات مقارنة باسترداد جدول كامل، خاصة إذا كان الفهرسة المناسبة موجودة.

استراتيجيات الترقيم الشائعة

هناك العديد من الاستراتيجيات الشائعة لتنفيذ الترقيم، ولكل منها مجموعة من المقايضات الخاصة بها. سنستكشف الأكثر شيوعًا: الترقيم القائم على الإزاحة/الحد (offset/limit) (الذي يشار إليه غالبًا بالترقيم المستند إلى الصفحة) والترقيم المستند إلى المؤشر (cursor-based) (المعروف أيضًا بترقيم المفاتيح أو البحث).

1. الترقيم القائم على الإزاحة/الحد (Offset/Limit) أو المستند إلى الصفحة

هذه هي بلا شك الطريقة الأكثر وضوحًا والأكثر اعتمادًا على نطاق واسع للترقيم. تعمل عن طريق السماح للعميل بتحديد معلمتين رئيسيتين:

بدلاً من ذلك، قد يحدد العملاء:

يمكن حساب offset من page و pageSize باستخدام الصيغة: offset = (page - 1) * pageSize.

خطوات التنفيذ التقني:

لنفترض أن لدينا نقطة نهاية API /items تُرجع قائمة بالعناصر.

أ. معلمات طلب API:
سيقوم العميل بتقديم طلب مثل:
GET /items?offset=20&limit=10 (جلب 10 عناصر، تخطي أول 20)
أو
GET /items?page=3&pageSize=10 (جلب الصفحة الثالثة، مع 10 عناصر في كل صفحة، وهو ما يعادل offset=20, limit=10).

من الممارسات الجيدة تعيين قيم افتراضية لهذه المعلمات (مثل limit=20، offset=0 أو page=1، pageSize=20) إذا لم يقدمها العميل. أيضًا، فرض حد أقصى لـ limit أو pageSize لمنع العملاء من طلب عدد كبير جدًا من السجلات، مما قد يجهد الخادم.

ب. منطق الواجهة الخلفية (مفاهيمي):
عندما يتلقى الخادم هذا الطلب، يحتاج إلى ترجمة هذه المعلمات إلى استعلام قاعدة بيانات.

// Example in Java with Spring Boot
@GetMapping("/items")
public ResponseEntity<PaginatedResponse<Item>> getItems(
    @RequestParam(defaultValue = "0") int offset,
    @RequestParam(defaultValue = "20") int limit
) {
    // Validate limit to prevent abuse
    if (limit > 100) {
        limit = 100; // Enforce a max limit
    }

    List<Item> items = itemRepository.findItemsWithOffsetLimit(offset, limit);
    long totalItems = itemRepository.countTotalItems(); // For metadata

    // Construct and return paginated response
    // ...
}

ج. استعلام قاعدة البيانات (مثال SQL):
تدعم معظم قواعد البيانات العلائقية عبارات offset و limit مباشرة.

لـ PostgreSQL أو MySQL:

SELECT *
FROM items
ORDER BY created_at DESC -- Consistent ordering is crucial for stable pagination
LIMIT 10 -- This is the 'limit' parameter
OFFSET 20; -- This is the 'offset' parameter

لـ SQL Server (قد تستخدم الإصدارات الأقدم ROW_NUMBER()):

SELECT *
FROM items
ORDER BY created_at DESC
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;

لـ Oracle:

SELECT *
FROM (
    SELECT i.*, ROWNUM rnum
    FROM (
        SELECT *
        FROM items
        ORDER BY created_at DESC
    ) i
    WHERE ROWNUM <= 20 + 10 -- offset + limit
)
WHERE rnum > 20; -- offset

ملاحظة هامة حول الترتيب: لكي يكون الترقيم القائم على الإزاحة/الحد موثوقًا به، يجب أن يتم فرز مجموعة البيانات الأساسية بواسطة مفتاح متناسق وفريد (أو شبه فريد)، أو مجموعة من المفاتيح. إذا كان ترتيب العناصر يمكن أن يتغير بين الطلبات (على سبيل المثال، يتم إدراج عناصر جديدة أو تحديث عناصر بطريقة تؤثر على ترتيب فرزها)، فقد يرى المستخدمون عناصر مكررة أو يفقدون عناصر عند التنقل بين الصفحات. الخيار الشائع هو الفرز حسب الطابع الزمني للإنشاء أو المعرف الأساسي.

د. هيكل استجابة API:
يجب أن تتضمن استجابة الترقيم الجيدة ليس فقط البيانات للصفحة الحالية ولكن أيضًا بيانات وصفية لمساعدة العميل على التنقل.

{
  "data": [
    // array of items for the current page
    { "id": "item_21", "name": "Item 21", ... },
    { "id": "item_22", "name": "Item 22", ... },
    // ... up to 'limit' items
    { "id": "item_30", "name": "Item 30", ... }
  ],
  "pagination": {
    "offset": 20,
    "limit": 10,
    "totalItems": 5000, // Total number of items available
    "totalPages": 500, // Calculated as ceil(totalItems / limit)
    "currentPage": 3 // Calculated as (offset / limit) + 1
  },
  "links": { // HATEOAS links for navigation
    "self": "/items?offset=20&limit=10",
    "first": "/items?offset=0&limit=10",
    "prev": "/items?offset=10&limit=10", // Null if on the first page
    "next": "/items?offset=30&limit=10", // Null if on the last page
    "last": "/items?offset=4990&limit=10"
  }
}

توفير روابط HATEOAS (Hypermedia as the Engine of Application State) (self, first, prev, next, last) هو أفضل ممارسة لـ REST. يسمح للعملاء بالتنقل عبر الصفحات دون الحاجة إلى بناء عناوين URL بأنفسهم.

إيجابيات الترقيم القائم على الإزاحة/الحد:

سلبيات الترقيم القائم على الإزاحة/الحد:

2. الترقيم المستند إلى المؤشر (Keyset/Seek)

يعالج الترقيم المستند إلى المؤشر بعض أوجه القصور في الترقيم القائم على الإزاحة/الحد، لا سيما الأداء مع مجموعات البيانات الكبيرة ومشكلات اتساق البيانات. بدلاً من الاعتماد على إزاحة مطلقة، فإنه يستخدم "مؤشرًا" يشير إلى عنصر معين في مجموعة البيانات. يطلب العميل بعد ذلك العناصر "بعد" أو "قبل" هذا المؤشر.

عادةً ما يكون المؤشر سلسلة مبهمة تقوم بترميز قيمة (قيم) مفتاح (مفاتيح) الفرز للعنصر الأخير الذي تم استرداده في الصفحة السابقة.

خطوات التنفيذ التقني:

أ. معلمات طلب API:
سيقوم العميل بتقديم طلب مثل:
GET /items?limit=10 (للصفحة الأولى)
وللصفحات اللاحقة:
GET /items?limit=10&after_cursor=opaquestringrepresentinglastitemid
أو، للترقيم للخلف (أقل شيوعًا ولكنه ممكن):
GET /items?limit=10&before_cursor=opaquestringrepresentingfirstitemid

لا تزال معلمة limit تحدد حجم الصفحة.

ب. ما هو المؤشر؟
يجب أن يكون المؤشر:

ج. منطق الواجهة الخلفية (مفاهيمي):

// Example in Java with Spring Boot
@GetMapping("/items")
public ResponseEntity<CursorPaginatedResponse<Item>> getItems(
    @RequestParam(defaultValue = "20") int limit,
    @RequestParam(required = false) String afterCursor
) {
    // Validate limit
    if (limit > 100) {
        limit = 100;
    }

    // Decode cursor to get the last seen item's properties
    // e.g., LastSeenItemDetails lastSeen = decodeCursor(afterCursor);
    // If afterCursor is null, it's the first page.

    List<Item> items;
    if (afterCursor != null) {
        DecodedCursor decoded = decodeCursor(afterCursor); // e.g., { lastId: "some_uuid", lastCreatedAt: "timestamp" }
        items = itemRepository.findItemsAfter(decoded.getLastCreatedAt(), decoded.getLastId(), limit);
    } else {
        items = itemRepository.findFirstPage(limit);
    }

    String nextCursor = null;
    if (!items.isEmpty() && items.size() == limit) {
        // Assuming items are sorted, the last item in the list is used to generate the next cursor
        Item lastItemOnPage = items.get(items.size() - 1);
        nextCursor = encodeCursor(lastItemOnPage.getCreatedAt(), lastItemOnPage.getId());
    }

    // Construct and return cursor paginated response
    // ...
}

// Helper methods for encoding/decoding cursors
// private DecodedCursor decodeCursor(String cursor) { ... }
// private String encodeCursor(Timestamp createdAt, String id) { ... }

د. استعلام قاعدة البيانات (مثال SQL):
المفتاح هو استخدام عبارة WHERE التي تقوم بتصفية السجلات بناءً على مفتاح (مفاتيح) الفرز من المؤشر. يجب أن تتوافق عبارة ORDER BY مع تكوين المؤشر.

بافتراض الفرز حسب created_at (تنازلي) ثم حسب id (تنازلي) كفاصل تعادل لترتيب مستقر إذا لم يكن created_at فريدًا:

للصفحة الأولى:

SELECT *
FROM items
ORDER BY created_at DESC, id DESC
LIMIT 10;

للصفحات اللاحقة، إذا تم فك ترميز المؤشر إلى last_created_at_from_cursor و last_id_from_cursor:

SELECT *
FROM items
WHERE (created_at, id) < (CAST('last_created_at_from_cursor' AS TIMESTAMP), CAST('last_id_from_cursor' AS UUID)) -- Or appropriate types
-- For ascending order, it would be >
-- The tuple comparison (created_at, id) < (val1, val2) is a concise way to write:
-- WHERE created_at < 'last_created_at_from_cursor'
--    OR (created_at = 'last_created_at_from_cursor' AND id < 'last_id_from_cursor')
ORDER BY created_at DESC, id DESC
LIMIT 10;

هذا النوع من الاستعلامات فعال للغاية، خاصة إذا كان هناك فهرس على (created_at, id). يمكن لقاعدة البيانات أن "تبحث" مباشرة عن نقطة البداية دون مسح الصفوف غير ذات الصلة.

هـ. هيكل استجابة API:

{
  "data": [
    // array of items for the current page
    { "id": "item_N", "createdAt": "2023-10-27T10:05:00Z", ... },
    // ... up to 'limit' items
    { "id": "item_M", "createdAt": "2023-10-27T10:00:00Z", ... }
  ],
  "pagination": {
    "limit": 10,
    "hasNextPage": true, // boolean indicating if there's more data
    "nextCursor": "base64encodedcursorstringforitem_M" // opaque string
    // Potentially a "prevCursor" if bi-directional cursors are supported
  },
  "links": {
    "self": "/items?limit=10&after_cursor=current_request_cursor_if_any",
    "next": "/items?limit=10&after_cursor=base64encodedcursorstringforitem_M" // Null if no next page
  }
}

لاحظ أن الترقيم المستند إلى المؤشر لا يوفر عادةً totalPages أو totalItems لأن حساب هذه يتطلب مسحًا كاملاً للجدول، مما يلغي بعض فوائد الأداء. إذا كانت هذه مطلوبة بشدة، يمكن توفير نقطة نهاية منفصلة أو تقدير.

إيجابيات الترقيم المستند إلى المؤشر:

سلبيات الترقيم المستند إلى المؤشر:

اختيار الاستراتيجية الصحيحة

يعتمد الاختيار بين الترقيم القائم على الإزاحة/الحد والترقيم المستند إلى المؤشر على متطلباتك المحددة:

في بعض الأنظمة، يتم استخدام نهج هجين، أو يتم تقديم استراتيجيات مختلفة لحالات استخدام أو نقاط نهاية مختلفة.

أفضل الممارسات لتنفيذ الترقيم

بغض النظر عن الاستراتيجية المختارة، التزم بأفضل الممارسات التالية:

  1. تسمية المعلمات المتسقة: استخدم أسماء واضحة ومتسقة لمعلمات الترقيم الخاصة بك (مثل limit، offset، page، pageSize، after_cursor، before_cursor). التزم بتقليد واحد (مثل camelCase أو snake_case) في جميع أنحاء واجهة برمجة التطبيقات الخاصة بك.
  2. توفير روابط التنقل (HATEOAS): كما هو موضح في أمثلة الاستجابة، قم بتضمين روابط لـ self، next، prev، first، و last (حيثما ينطبق ذلك). هذا يجعل واجهة برمجة التطبيقات أكثر قابلية للاكتشاف ويفصل العميل عن منطق بناء عناوين URL.
  3. القيم الافتراضية والحدود القصوى:
  1. توثيق واضح لواجهة برمجة التطبيقات: قم بتوثيق استراتيجية الترقيم الخاصة بك بشكل شامل:
  1. الفرز المتسق: تأكد من أن البيانات الأساسية مرتبة بشكل متسق لكل طلب ترقيم. بالنسبة للإزاحة/الحد، هذا أمر حيوي لتجنب انحراف البيانات. بالنسبة للترقيم المستند إلى المؤشر، يحدد ترتيب الفرز كيفية بناء وتفسير المؤشرات. استخدم عمود فاصل تعادل فريد (مثل المعرف الأساسي) إذا كان عمود الفرز الأساسي يمكن أن يحتوي على قيم مكررة.
  2. التعامل مع الحالات الهامشية:
  1. اعتبارات العدد الإجمالي:
  1. معالجة الأخطاء: أعد رموز حالة HTTP المناسبة للأخطاء (مثل 400 للإدخال السيئ، 500 لأخطاء الخادم أثناء جلب البيانات).
  2. الأمان: على الرغم من أنها ليست آلية ترقيم مباشرة، تأكد من أن البيانات التي يتم ترقيمها تحترم قواعد التفويض. يجب أن يكون المستخدم قادرًا فقط على الترقيم عبر البيانات التي يُسمح له برؤيتها.
  3. التخزين المؤقت: يمكن غالبًا تخزين استجابات الترقيم مؤقتًا. بالنسبة للترقيم المستند إلى الإزاحة، GET /items?page=2&pageSize=10 قابل للتخزين المؤقت بشكل كبير. بالنسبة للترقيم المستند إلى المؤشر، GET /items?limit=10&after_cursor=XYZ قابل للتخزين المؤقت أيضًا. تأكد من أن استراتيجية التخزين المؤقت الخاصة بك تعمل بشكل جيد مع كيفية إنشاء واستهلاك روابط الترقيم. يجب النظر في استراتيجيات الإلغاء إذا تغيرت البيانات الأساسية بشكل متكرر.

مواضيع متقدمة (إشارات موجزة)

الخلاصة

التنفيذ الصحيح للترقيم أمر أساسي لبناء واجهات برمجة تطبيقات REST قابلة للتطوير وسهلة الاستخدام. بينما الترقيم القائم على الإزاحة/الحد أبسط للبدء به، يوفر الترقيم المستند إلى المؤشر أداءً واتساقًا فائقين لمجموعات البيانات الكبيرة والديناميكية. من خلال فهم التفاصيل التقنية لكل استراتيجية، واختيار الاستراتيجية التي تناسب احتياجات تطبيقك بشكل أفضل، واتباع أفضل الممارسات للتنفيذ وتصميم واجهة برمجة التطبيقات، يمكنك ضمان أن واجهة برمجة التطبيقات الخاصة بك تقدم البيانات بكفاءة لعملائك، بغض النظر عن النطاق. تذكر دائمًا إعطاء الأولوية للتوثيق الواضح ومعالجة الأخطاء القوية لتوفير تجربة سلسة لمستهلكي واجهة برمجة التطبيقات.


ممارسة تصميم API في Apidog

اكتشف طريقة أسهل لبناء واستخدام واجهات برمجة التطبيقات