تصميم واجهات البرمجة وأنماط الخدمات المصغرة

أفضل ممارسات تصميم واجهات RESTful البرمجية

5 دقيقة للقراءة

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

اصطلاحات تسمية الموارد

تُنمذج واجهات REST البرمجية الموارد كأسماء وليس أفعال. استخدم أسماء الجمع والمسارات الهرمية للتعبير عن العلاقات.

GET    /users                    # سرد جميع المستخدمين
POST   /users                    # إنشاء مستخدم
GET    /users/{id}               # الحصول على مستخدم محدد
PUT    /users/{id}               # استبدال مستخدم
PATCH  /users/{id}               # تحديث جزئي لمستخدم
DELETE /users/{id}               # حذف مستخدم

GET    /users/{id}/orders        # سرد طلبات مستخدم
POST   /users/{id}/orders        # إنشاء طلب لمستخدم
GET    /users/{id}/orders/{oid}  # الحصول على طلب محدد

القواعد الأساسية:

  • استخدم أسماء الجمع: /users وليس /user، /orders وليس /order
  • استخدم الشرطات للموارد متعددة الكلمات: /payment-methods وليس /paymentMethods
  • استخدم التسلسل الهرمي للعلاقات: /users/{id}/orders وليس /getUserOrders
  • لا تستخدم أفعالًا في عناوين URL أبدًا: /users/{id}/activate مقبول فقط كإجراء تحكم، يُفضل PATCH /users/{id} مع { "status": "active" }

طرق HTTP ورموز الحالة

لكل طريقة HTTP دلالات محددة. إتقان رموز الحالة يدل على الخبرة.

الطريقةالغرضقابلة للتكرارآمنة
GETقراءة موردنعمنعم
POSTإنشاء موردلالا
PUTاستبدال موردنعملا
PATCHتحديث جزئيلالا
DELETEحذف موردنعملا

رموز الحالة التي يجب أن تعرفها

الرمزالمعنىمتى تستخدمه
200 OKنجاحGET وPUT وPATCH مع جسم استجابة
201 Createdتم إنشاء الموردPOST — أضف ترويسة Location
204 No Contentنجاح بدون جسمDELETE وPUT/PATCH بدون جسم مُرجع
301 Moved Permanentlyإعادة توجيه دائمةترحيل إصدار الواجهة البرمجية
304 Not Modifiedالاستجابة المخزنة صالحةGET مع If-None-Match / ETag
400 Bad Requestإدخال غير صالحJSON مشوه، حقول مطلوبة مفقودة
401 Unauthorizedغير مصادقرمز مفقود أو غير صالح
403 Forbiddenغير مخوّلرمز صالح لكن صلاحيات غير كافية
404 Not Foundالمورد غير موجودالمعرف غير موجود
409 Conflictتعارض في الحالةإنشاء مكرر، عدم تطابق الإصدار
429 Too Many Requestsتجاوز حد المعدلأضف ترويسة Retry-After
500 Internal Server Errorخطأ في الخادماستثناء غير معالج
503 Service Unavailableانقطاع مؤقتصيانة، تعطل تبعية

نصيحة للمقابلة: معرفة الفرق بين 401 (من أنت؟) و403 (أعرف من أنت لكن لا يمكنك فعل هذا) سؤال مقابلة شائع.

أنماط التصفح بالصفحات

كل نقطة نهاية تسرد قائمة تحتاج تصفحًا بالصفحات. للنهجين الرئيسيين مقايضات مميزة.

التصفح بالإزاحة

GET /users?page=2&limit=20
{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total_count": 354,
    "total_pages": 18
  }
}

التصفح بالمؤشر

GET /users?limit=20&cursor=eyJpZCI6MTAwfQ==
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ==",
    "prev_cursor": "eyJpZCI6MTAxfQ==",
    "has_more": true
  }
}

المقارنة

الجانبالتصفح بالإزاحةالتصفح بالمؤشر
القفز للصفحة Nنعملا
التناسق مع الإدراج/الحذفلا (الصفوف تتحرك)نعم
الأداء في الإزاحات العميقةضعيف (OFFSET يمسح)ثابت (WHERE id > cursor)
العدد الإجمالي متاحنعم (لكنه مكلف)ليس بسهولة
تعقيد التنفيذبسيطمتوسط
الأفضل لـلوحات الإدارة، مجموعات بيانات صغيرةالتغذيات، البيانات الفورية، مجموعات كبيرة

إجابة المقابلة: "سأستخدم التصفح بالمؤشر لتغذية وسائل التواصل الاجتماعي لأن المستخدمين يضيفون منشورات جديدة باستمرار. التصفح بالإزاحة سيتسبب في انتقال العناصر بين الصفحات. المؤشر يضمن موضعًا مستقرًا في مجموعة النتائج."

التصفية والترتيب واختيار الحقول

استخدم معلمات الاستعلام لمنح العملاء التحكم في الاستجابات.

GET /users?status=active&role=admin&sort=-created_at,name&fields=id,name,email
  • التصفية: ?status=active&role=admin — مرشحات المساواة كأزواج مفتاح-قيمة
  • الترتيب: ?sort=-created_at,name — البادئة - للترتيب التنازلي، فاصلة للترتيب المتعدد
  • اختيار الحقول: ?fields=id,name,email — تقليل حجم الحمولة، مفيد لعملاء الجوال
// TypeScript: تحليل معلمات الترتيب على الخادم
function parseSortParam(sort: string): Array<{ field: string; order: 'asc' | 'desc' }> {
  return sort.split(',').map(field => ({
    field: field.replace(/^-/, ''),
    order: field.startsWith('-') ? 'desc' : 'asc'
  }));
}

// parseSortParam("-created_at,name")
// => [{ field: "created_at", order: "desc" }, { field: "name", order: "asc" }]

أنماط المصادقة

JWT (رموز JSON للويب)

أكثر أنماط مصادقة الواجهات البرمجية شيوعًا. يستخدم رمز وصول قصير العمر ورمز تحديث طويل العمر.

رمز الوصول:   15 دقيقة مدة صلاحية — يُرسل في ترويسة Authorization
رمز التحديث: 7 أيام مدة صلاحية — يُخزن في كوكي httpOnly، يُستخدم للحصول على رموز وصول جديدة
// بنية JWT: الترويسة.الحمولة.التوقيع
interface JWTPayload {
  sub: string;       // معرف المستخدم
  iat: number;       // وقت الإصدار (epoch)
  exp: number;       // وقت الانتهاء (epoch)
  roles: string[];   // مطالبات التخويل
}

// التحقق من جهة الخادم
import jwt from 'jsonwebtoken';

function authenticate(req: Request): JWTPayload {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) throw new UnauthorizedError('رمز مفقود');

  try {
    return jwt.verify(token, process.env.JWT_SECRET) as JWTPayload;
  } catch (err) {
    throw new UnauthorizedError('رمز غير صالح أو منتهي الصلاحية');
  }
}

تدفقات OAuth2

التدفقحالة الاستخدامالأمان
Authorization Codeتطبيقات الويب (جهة الخادم)الرمز يُتبادل من جهة الخادم، السر لا يُكشف أبدًا
Authorization Code + PKCEالجوال/SPAمُحقق الرمز يمنع الاعتراض، لا حاجة لسر العميل
Client Credentialsخدمة-إلى-خدمةآلة-إلى-آلة، بدون سياق مستخدم

مفاتيح الواجهة البرمجية

الأفضل للتواصل بين الخدمات. مرره عبر ترويسة، وليس في عنوان URL أبدًا.

X-API-Key: sk_live_abc123def456

تحديد المعدل

يحمي تحديد المعدل واجهتك البرمجية من سوء الاستخدام ويضمن الاستخدام العادل. خوارزمية دلو الرموز هي الأكثر سؤالًا عنها في المقابلات.

خوارزمية دلو الرموز

import time

class TokenBucket:
    def __init__(self, capacity: int, refill_rate: float):
        self.capacity = capacity          # أقصى عدد رموز في الدلو
        self.tokens = capacity            # الرموز الحالية
        self.refill_rate = refill_rate    # الرموز المضافة في الثانية
        self.last_refill = time.time()

    def allow_request(self) -> bool:
        self._refill()
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(
            self.capacity,
            self.tokens + elapsed * self.refill_rate
        )
        self.last_refill = now

# مثال: سعة 100 طلب/دقيقة، يُعاد الملء بمعدل ~1.67 رمز/ثانية
bucket = TokenBucket(capacity=100, refill_rate=100/60)

استراتيجيات تحديد المعدل

الاستراتيجيةكيف تعملالمزاياالعيوب
دلو الرموزالدلو يحمل رموزًا ويُعاد ملؤه بمعدل ثابتيسمح بالاندفاعات، معدل سلسذاكرة أكثر قليلاً
سجل النافذة المنزلقةتخزين طابع زمني لكل طلبدقيق جدًااستخدام ذاكرة عالٍ
عداد النافذة المنزلقةعد مرجح عبر النافذة الحالية والسابقةذاكرة منخفضة، دقيق إلى حد ماتقريبي

أرجع دائمًا ترويسات تحديد المعدل حتى يتمكن العملاء من تنظيم أنفسهم:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1704067200
Retry-After: 30

إصدار الواجهات البرمجية

الاستراتيجيةمثالالمزاياالعيوب
مسار URI/v1/usersبسيط، صريح، سهل التوجيهعناوين URL تتغير، صعب الإيقاف التدريجي
ترويسةAccept: application/vnd.api.v1+jsonعناوين URL نظيفة، مرنمخفي عن المتصفح، أصعب في الاختبار
معلمة استعلام/users?version=1سهل الإضافةيلوث سلسلة الاستعلام، الاختياري قد يسبب أخطاء

الإجابة الشائعة: "إصدار مسار URI هو الأكثر عملية. إنه صريح وسهل التوجيه عند موازن الحمل وبسيط لفهم العملاء. إصدار الترويسة أنظف نظريًا لكنه يضيف احتكاكًا للمطورين الذين يستخدمون المتصفحات أو curl."

مفاتيح قابلية التكرار

طلبات POST ليست قابلة للتكرار — إعادة محاولة دفعة قد تخصم من العميل مرتين. مفاتيح قابلية التكرار تحل هذه المشكلة.

// العميل يرسل مفتاحًا فريدًا مع الطلب
// POST /payments
// Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

async function processPayment(req: Request) {
  const idempotencyKey = req.headers['idempotency-key'];
  if (!idempotencyKey) {
    return res.status(400).json({ error: 'ترويسة Idempotency-Key مطلوبة' });
  }

  // تحقق هل عالجنا هذا المفتاح سابقًا
  const existing = await redis.get(`idempotency:${idempotencyKey}`);
  if (existing) {
    return res.status(200).json(JSON.parse(existing)); // أرجع الاستجابة المخزنة
  }

  // عالج الدفعة
  const result = await chargeCustomer(req.body);

  // خزّن النتيجة (مدة الصلاحية: 24 ساعة)
  await redis.set(
    `idempotency:${idempotencyKey}`,
    JSON.stringify(result),
    'EX', 86400
  );

  return res.status(201).json(result);
}

كيف يعمل: يولّد العميل UUID ويرسله مع كل طلب POST. يخزن الخادم الاستجابة مفتاحة بذلك الـ UUID. إذا وصل نفس المفتاح مرة أخرى (إعادة محاولة الشبكة، نقر مزدوج من المستخدم)، يرجع الخادم الاستجابة المخزنة بدون إعادة المعالجة.

HATEOAS

الوسائط الفائقة كمحرك لحالة التطبيق يعني أن الاستجابات تتضمن روابط للإجراءات المرتبطة.

{
  "id": "order-123",
  "status": "pending",
  "total": 99.99,
  "_links": {
    "self": { "href": "/orders/order-123" },
    "cancel": { "href": "/orders/order-123/cancel", "method": "POST" },
    "payment": { "href": "/orders/order-123/payment", "method": "POST" }
  }
}

في الممارسة العملية، معظم الواجهات البرمجية لا تنفذ HATEOAS بالكامل. لكن ذكرها في المقابلة يُظهر أنك تفهم مستويات نضج REST (نموذج نضج Richardson).

التالي: سنستكشف gRPC مع Protocol Buffers وGraphQL — البديلين الرئيسيين لـ REST. :::

اختبار

اختبار الوحدة 3: تصميم واجهات البرمجة وأنماط الخدمات المصغرة

خذ الاختبار
نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.