فهم أنماط بوابة API
بوابة API هي خادم يعمل كنقطة دخول لتطبيقات العملاء للوصول إلى خدمات الخلفية. تجلس بين العملاء والخدمات المصغرة، وتوفر واجهة موحدة بينما تتعامل مع المخاوف المشتركة مثل المصادقة، والحد من المعدل، وموازنة التحميل، وتوجيه الطلبات. بوابات API ضرورية لإدارة البنى الموزعة المعقدة.
ما هي بوابة API؟
بوابة API هي وكيل عكسي يقبل استدعاءات API من العملاء، ويجمع الخدمات المختلفة المطلوبة لتلبيتها، ويعيد النتيجة المناسبة. فكر فيها كشرطي مرور لنظام API الخاص بك - فهي توجه الطلبات إلى الخدمات المناسبة وتفرض السياسات.
مفهوم أساسي: نمط بوابة API ينفذ نمط "الواجهة الخلفية للواجهة الأمامية" (BFF)، حيث يمكن للعملاء المختلفين (الويب، الموبايل، إنترنت الأشياء) أن يكون لديهم تنفيذات بوابة مخصصة مصممة لاحتياجاتهم المحددة.
المسؤوليات الأساسية لبوابة API
1. توجيه الطلبات
تقوم البوابة بتعيين الطلبات الواردة إلى خدمات الخلفية المناسبة بناءً على أنماط URL أو الرؤوس أو معايير أخرى.
# تكوين توجيه Nginx
location /api/users/ {
proxy_pass http://user-service:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/products/ {
proxy_pass http://product-service:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/orders/ {
proxy_pass http://order-service:3002/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
2. المصادقة والتفويض
مركزية منطق المصادقة على مستوى البوابة لتجنب تكرار كود الأمان عبر الخدمات.
// مصادقة بوابة API في Express.js
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// middleware المصادقة
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'لم يتم توفير رمز' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'رمز غير صالح' });
}
req.user = user;
next();
});
};
// تطبيق المصادقة على جميع مسارات /api
app.use('/api', authenticateToken);
// مسار إلى خدمة المستخدم
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3000',
changeOrigin: true,
pathRewrite: { '^/api/users': '/' },
onProxyReq: (proxyReq, req) => {
// إعادة توجيه معلومات المستخدم إلى خدمات الخلفية
proxyReq.setHeader('X-User-Id', req.user.id);
proxyReq.setHeader('X-User-Role', req.user.role);
}
}));
أفضل ممارسة: استخدم رموز JWT مع أوقات انتهاء قصيرة ونفذ آليات تحديث الرمز. قم بتخزين العمليات الحساسة (مثل أدوار المستخدم) في حمولة الرمز لتجنب عمليات بحث إضافية في قاعدة البيانات.
استراتيجيات موازنة التحميل
توزع بوابات API الطلبات الواردة عبر نسخ متعددة من خدمات الخلفية لضمان التوفر العالي والأداء الأمثل.
خوارزميات موازنة التحميل الشائعة
1. Round Robin: توزع الطلبات بشكل متسلسل عبر الخوادم المتاحة.
# موازنة التحميل round-robin في Nginx
upstream user_service {
server user-service-1:3000;
server user-service-2:3000;
server user-service-3:3000;
}
location /api/users/ {
proxy_pass http://user_service/;
}
2. الاتصالات الأقل: توجه الطلبات إلى الخادم الذي لديه أقل عدد من الاتصالات النشطة.
upstream user_service {
least_conn;
server user-service-1:3000;
server user-service-2:3000;
server user-service-3:3000;
}
3. IP Hash: يضمن أن نفس العميل يصل دائمًا إلى نفس الخادم (مفيد للجلسات الثابتة).
upstream user_service {
ip_hash;
server user-service-1:3000;
server user-service-2:3000;
server user-service-3:3000;
}
4. موازنة التحميل الموزونة: تخصص المزيد من الحركة للخوادم القوية.
upstream user_service {
server user-service-1:3000 weight=3; # يحصل على 60% من الحركة
server user-service-2:3000 weight=2; # يحصل على 40% من الحركة
}
فحوصات الصحة: قم دائمًا بتنفيذ فحوصات الصحة لإزالة الخوادم غير الصحية تلقائيًا من مجموعة موازنة التحميل. يمكن لـ Nginx إجراء فحوصات صحة سلبية، بينما يوفر Nginx Plus فحوصات صحة نشطة.
تحويل الطلبات والاستجابات
يمكن للبوابات تعديل الطلبات قبل إعادة توجيهها وتحويل الاستجابات قبل الإرجاع إلى العملاء.
// تحويل الطلب/الاستجابة في Express.js
const axios = require('axios');
app.get('/api/products/:id', authenticateToken, async (req, res) => {
try {
// جلب من خدمات متعددة
const [product, reviews, inventory] = await Promise.all([
axios.get(`http://product-service:3001/products/${req.params.id}`),
axios.get(`http://review-service:3002/reviews?productId=${req.params.id}`),
axios.get(`http://inventory-service:3003/inventory/${req.params.id}`)
]);
// تجميع وتحويل الاستجابة
const aggregatedResponse = {
id: product.data.id,
name: product.data.name,
price: product.data.price,
description: product.data.description,
averageRating: calculateAverageRating(reviews.data),
reviewCount: reviews.data.length,
inStock: inventory.data.quantity > 0,
stockQuantity: inventory.data.quantity,
// إضافة حقول محسوبة
isOnSale: product.data.salePrice < product.data.price,
savings: product.data.price - product.data.salePrice
};
res.json(aggregatedResponse);
} catch (error) {
console.error('خطأ في البوابة:', error);
res.status(500).json({ error: 'فشل في جلب تفاصيل المنتج' });
}
});
function calculateAverageRating(reviews) {
if (reviews.length === 0) return 0;
const sum = reviews.reduce((acc, review) => acc + review.rating, 0);
return (sum / reviews.length).toFixed(1);
}
الحد من المعدل والتحكم
حماية خدمات الخلفية من الحمل الزائد من خلال تنفيذ الحد من المعدل على مستوى البوابة.
// الحد من المعدل في Express مع redis
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis({
host: 'localhost',
port: 6379
});
// إنشاء محدد المعدل
const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rate_limit:',
}),
windowMs: 15 * 60 * 1000, // 15 دقيقة
max: 100, // حد كل IP إلى 100 طلب لكل نافذة زمنية
message: {
error: 'طلبات كثيرة جدًا من هذا IP، يرجى المحاولة لاحقًا.'
},
standardHeaders: true, // إرجاع معلومات حد المعدل في الرؤوس
legacyHeaders: false,
});
// تطبيق الحد من المعدل على مسارات API
app.use('/api/', apiLimiter);
// حدود مختلفة لنقاط النهاية المختلفة
const strictLimiter = rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 15 * 60 * 1000,
max: 10, // حد أكثر صرامة للنقاط الحساسة
});
app.use('/api/auth/login', strictLimiter);
app.use('/api/auth/register', strictLimiter);
# الحد من المعدل في Nginx
# تعريف منطقة حد المعدل (10MB يمكن أن يخزن ~160,000 عنوان IP)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location /api/ {
# تطبيق حد المعدل (السماح بدفعات من 20 طلب)
limit_req zone=api_limit burst=20 nodelay;
# استجابة خطأ مخصصة
limit_req_status 429;
proxy_pass http://backend_services;
}
حلول بوابة API الشائعة
Kong API Gateway
Kong هي بوابة API سحابية أصلية ومستقلة عن المنصة مبنية على Nginx. توفر نظامًا بيئيًا واسعًا للمكونات الإضافية وميزات المؤسسة.
# مثال تكوين Kong (تكوين تصريحي)
_format_version: "2.1"
services:
- name: user-service
url: http://user-service:3000
routes:
- name: user-routes
paths:
- /api/users
plugins:
- name: rate-limiting
config:
minute: 100
policy: local
- name: jwt
config:
secret_is_base64: false
- name: cors
config:
origins:
- "*"
methods:
- GET
- POST
- PUT
- DELETE
headers:
- Authorization
- Content-Type
- name: product-service
url: http://product-service:3001
routes:
- name: product-routes
paths:
- /api/products
plugins:
- name: response-transformer
config:
add:
headers:
- X-Service:product-service
مزايا Kong: بنية المكونات الإضافية، المصادقة/التفويض المدمج، التكوين التصريحي، قدرات شبكة الخدمة، والأداء الممتاز. إنه مناسب بشكل خاص لبنى الخدمات المصغرة.
AWS API Gateway
خدمة مُدارة بالكامل تجعل من السهل إنشاء ونشر وصيانة ومراقبة وتأمين واجهات برمجة التطبيقات على أي مقياس.
// AWS API Gateway مع تكامل Lambda (Serverless Framework)
service: api-gateway-example
provider:
name: aws
runtime: nodejs18.x
region: us-east-1
functions:
getUsers:
handler: handlers/users.get
events:
- http:
path: users
method: get
cors: true
authorizer:
name: authFunction
resultTtlInSeconds: 300
createUser:
handler: handlers/users.create
events:
- http:
path: users
method: post
cors: true
authorizer:
name: authFunction
authFunction:
handler: handlers/auth.authorize
resources:
Resources:
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: MyAPI
# إعدادات التحكم في API
ApiGatewayUsagePlan:
Type: AWS::ApiGateway::UsagePlan
Properties:
UsagePlanName: BasicPlan
Throttle:
BurstLimit: 200
RateLimit: 100
نمط قاطع الدائرة مع بوابة API
منع الفشل المتتالي من خلال تنفيذ قواطع الدائرة على مستوى البوابة.
// تنفيذ قاطع الدائرة باستخدام opossum
const CircuitBreaker = require('opossum');
const axios = require('axios');
// دالة لاستدعاء خدمة الخلفية
async function callUserService(userId) {
const response = await axios.get(`http://user-service:3000/users/${userId}`);
return response.data;
}
// خيارات قاطع الدائرة
const options = {
timeout: 3000, // إذا استغرقت الدالة أكثر من 3 ثوانٍ، أطلق الفشل
errorThresholdPercentage: 50, // عندما تفشل 50% من الطلبات، افتح الدائرة
resetTimeout: 10000, // بعد 10 ثوانٍ، حاول مرة أخرى (حالة نصف مفتوحة)
volumeThreshold: 10, // الحد الأدنى لعدد الطلبات قبل التعثر
};
// إنشاء قاطع الدائرة
const breaker = new CircuitBreaker(callUserService, options);
// معالجة أحداث الدائرة
breaker.on('open', () => {
console.log('قاطع الدائرة مفتوح - الخدمة معطلة');
});
breaker.on('halfOpen', () => {
console.log('قاطع الدائرة نصف مفتوح - اختبار الخدمة');
});
breaker.on('close', () => {
console.log('قاطع الدائرة مغلق - الخدمة صحية');
});
// استخدام في مسار Express
app.get('/api/users/:id', async (req, res) => {
try {
const user = await breaker.fire(req.params.id);
res.json(user);
} catch (error) {
if (breaker.opened) {
// الدائرة مفتوحة، أعد البيانات المخزنة مؤقتًا أو البديل
res.status(503).json({
error: 'الخدمة غير متوفرة مؤقتًا',
cached: true
});
} else {
res.status(500).json({ error: 'خطأ داخلي في الخادم' });
}
}
});
تحذير: يجب تكوين قواطع الدائرة بعناية. تحديد عتبات منخفضة جدًا يمكن أن يسبب نتائج إيجابية خاطئة، في حين أن تحديدها مرتفعة جدًا يهزم الغرض من وجود قاطع دائرة.
تجميع وتكوين الطلبات
يمكن لبوابات API دمج مكالمات خلفية متعددة في طلب عميل واحد، مما يقلل من التحميل على الشبكة.
// التجميع على طراز GraphQL في بوابة REST API
app.get('/api/dashboard/:userId', authenticateToken, async (req, res) => {
const { userId } = req.params;
try {
// طلبات متوازية إلى خدمات متعددة
const [user, orders, recommendations, notifications] = await Promise.allSettled([
axios.get(`http://user-service:3000/users/${userId}`),
axios.get(`http://order-service:3001/orders?userId=${userId}&limit=5`),
axios.get(`http://recommendation-service:3002/recommendations/${userId}`),
axios.get(`http://notification-service:3003/notifications/${userId}?unread=true`)
]);
// بناء الاستجابة المجمعة
const dashboard = {
user: user.status === 'fulfilled' ? user.value.data : null,
recentOrders: orders.status === 'fulfilled' ? orders.value.data : [],
recommendations: recommendations.status === 'fulfilled' ? recommendations.value.data : [],
unreadNotifications: notifications.status === 'fulfilled' ? notifications.value.data : [],
// إضافة البيانات الوصفية
loadedAt: new Date().toISOString(),
partialFailure: [user, orders, recommendations, notifications].some(r => r.status === 'rejected')
};
res.json(dashboard);
} catch (error) {
console.error('خطأ في تجميع لوحة القيادة:', error);
res.status(500).json({ error: 'فشل في تحميل لوحة القيادة' });
}
});
التخزين المؤقت على مستوى البوابة
تنفيذ التخزين المؤقت للاستجابة لتقليل الحمل على الخلفية وتحسين الأداء.
// التخزين المؤقت للاستجابة المستند إلى Redis
const redis = require('redis');
const client = redis.createClient({ url: 'redis://localhost:6379' });
client.on('error', err => console.error('خطأ Redis:', err));
await client.connect();
// middleware التخزين المؤقت
const cacheMiddleware = (duration) => {
return async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
try {
// فحص ذاكرة التخزين المؤقت
const cachedResponse = await client.get(key);
if (cachedResponse) {
console.log('إصابة ذاكرة التخزين المؤقت:', key);
return res.json(JSON.parse(cachedResponse));
}
// تجاوز res.json لتخزين الاستجابة مؤقتًا
const originalJson = res.json.bind(res);
res.json = (data) => {
// تخزين الاستجابة مؤقتًا
client.setEx(key, duration, JSON.stringify(data))
.catch(err => console.error('خطأ في تعيين ذاكرة التخزين المؤقت:', err));
return originalJson(data);
};
next();
} catch (error) {
console.error('خطأ في middleware التخزين المؤقت:', error);
next(); // المتابعة بدون تخزين مؤقت عند حدوث خطأ
}
};
};
// تطبيق التخزين المؤقت على مسارات محددة
app.get('/api/products', cacheMiddleware(300), async (req, res) => {
const response = await axios.get('http://product-service:3001/products');
res.json(response.data);
});
تمرين: صمم بوابة API لمنصة تجارة إلكترونية مع المتطلبات التالية:
- ثلاث خدمات خلفية: خدمة المستخدم (المنفذ 3000)، خدمة المنتج (المنفذ 3001)، خدمة الطلب (المنفذ 3002)
- تنفيذ مصادقة JWT لجميع المسارات
- تطبيق الحد من المعدل: 100 طلب لكل 15 دقيقة للنقاط العامة، 10 طلبات لكل 15 دقيقة لنقاط المصادقة
- إنشاء نقطة نهاية مجمعة /api/checkout/:orderId تجلب تفاصيل الطلب ومعلومات المستخدم ومعلومات المنتج في طلب واحد
- تنفيذ التخزين المؤقت للاستجابة لقوائم المنتجات (5 دقائق TTL)
- إضافة نقاط نهاية فحص الصحة لكل خدمة خلفية
اختر إما Nginx أو Kong أو Express.js وقدم التكوين الكامل.