في دليل أمن واجهات API ذكرنا أن بوابة ال API Gateway تتحقق من التوكن وتحد عدد الطلبات وتوجه كل نسخة إلى خدمتها، وفي تحديد معدل الطلبات قلنا إن البوابة هي المكان الطبيعي لأغلب الحدود، ومرت كلمة «البوابة» في دراسات الحالة مثل نظام حجز التذاكر ونظام الإشعارات كمربع صغير في الرسم دون أن نتوقف عنده. والمشكلة التي تحلها البوابة تظهر عندما يصبح عندك تطبيق جوال وموقع ويب وشركاء يتصلون بك، وأمامهم ثلاث خدمات أو أكثر، فتجد أن كل خدمة تكتب نفس الكود: تقرأ التوكن وتتحقق منه، وتعد الطلبات، وتضيف ترويسات CORS، وتكتب سجلاً بصيغتها الخاصة، وكل نسخة من هذا الكود فيها خطأ مختلف عن أختها.
وسوف نناقش في هذا المقال ما يلي:
- ما هي البوابة، وما الفرق بينها وبين ال Reverse Proxy وموزع الحمل Load Balancer وال Service Mesh ونمط الخلفية للواجهة Backend for Frontend (BFF)، في جدول واحد.
- مسؤوليات البوابة واحدة واحدة: التوجيه والإصدارات، والمصادقة مقابل الصلاحيات، والحدود والحصص، وتحويل الطلب، والتخزين المؤقت، وCORS، وحماية الخدمات البطيئة، والمراقبة، ومفاتيح المطورين.
- تجربة عملية: نجعل Nginx بوابة صغيرة بمفاتيح API وحد لكل عميل، ثم نرى أين يتوقف.
- أشكال وضع البوابة: بوابة واحدة أم بوابة لكل فريق، وال BFF، والحركة الداخلية والخارجية، وواجهة Gateway API في Kubernetes.
- الأخطاء التي تحول البوابة من حل إلى مشكلة، ومتى لا تحتاجها أصلاً.
دليلأمن ال API بالأمثلة مع OWASP API Security Top 10أشهر عشرة مخاطر في واجهات API كما ترتبها OWASP، ولكل خطر مثال لكود ضعيف وكيف تصلحه وتختبره.
دليلتحديد معدل الطلبات Rate Limiting وخوارزمياتهكيف تعمل خوارزميات تحديد معدل الطلبات، وأيها يناسب واجهتك، وكيف تطبقها على عدة سيرفرات وترد بالرمز 429 الصحيح.
دليلكيف تمنع خدمة بطيئة من إسقاط نظامك كله؟لماذا تسقط خدمة بطيئة واحدة تطبيقك السليم، وكيف تحميه بالمهلة وإعادة المحاولة المحدودة وقاطع الدائرة ورفض الزائد.ما هي بوابة ال API Gateway؟
بوابة ال API Gateway برنامج يقف أمام خدماتك ويستقبل كل طلبات العملاء على عنوان واحد، ثم يقرر لكل طلب: هل هو مسموح، ومن صاحبه، وكم طلباً أرسل، وإلى أي خدمة يذهب، ثم يرسله إليها ويعيد ردها، ويسجل كل ذلك في مكان واحد. وهي تشبه مكتب الاستقبال في مبنى حكومي، فالموظف عند الباب يتحقق من الهوية ويعطيك رقم انتظار ويوجهك إلى القسم الصحيح، ولكنه لا يقرر إن كانت معاملتك مقبولة، فهذا قرار القسم نفسه. ويصف نمط API Gateway في microservices.io البوابة بأنها نقطة الدخول الوحيدة لكل العملاء، وأنها قد تعطي كل نوع من العملاء واجهة مختلفة.
والصورة التالية تبين الفرق بين نظام بلا بوابة ونظام فيه بوابة:

وقد يتساءل البعض: لماذا لا نكتب هذه الفحوصات في مكتبة مشتركة تستخدمها كل الخدمات؟ والإجابة أن المكتبة حل جيد عندما تكون كل الخدمات بلغة واحدة ويحدثها فريق واحد، ولكن عندما تكون خدمة بلغة Go وأخرى بلغة Python وثالثة قديمة لا يجرؤ أحد على تحديثها، فإن كل تغيير في سياسة الأمان يحتاج إلى ثلاثة إصدارات وثلاثة نشرات، بينما البوابة تغير السياسة في مكان واحد وتطبقها على الجميع في نفس اللحظة.
البوابة وأقاربها: Reverse Proxy وموزع الحمل وService Mesh وBFF
هذه الخمسة تتداخل في الأسماء وفي الأدوات، فبرنامج Nginx مثلاً يمكن أن يكون أي واحد منها بحسب الإعداد، لذلك الأفضل أن تفرق بينها بالسؤال: أين يقف، وماذا يعرف عن الطلب؟
- ال Reverse Proxy: يقف أمام تطبيقاتك ويوجه الطلب بحسب النطاق Domain والمسار Path، ويتولى شهادات TLS، ولكنه لا يعرف من هو العميل، وقد شرحنا أشهر أدواته في مقارنة أدوات ال Reverse Proxy.
- موزع الحمل Load Balancer: يوزع الطلبات على عدة نسخ من نفس الخدمة ويخرج النسخة المعطلة، وقد يعمل في الطبقة الرابعة على مستوى TCP دون أن يقرأ الطلب أصلاً، كما في دليل HAProxy.
- بوابة ال API Gateway: Reverse Proxy يعرف العميل، فيقرأ مفتاح ال API أو التوكن، ويعرف أن هذا الطلب من «تطبيق الجوال» وأن هذا العميل استهلك 900 طلب من حصته اليومية، ويطبق السياسات لكل عميل ولكل مسار.
- شبكة الخدمات Service Mesh: لا تقف على الحافة وإنما بين خدماتك من الداخل، فبجانب كل خدمة Proxy صغير يسمى Sidecar يشفر الاتصال بين الخدمات بال mTLS ويعيد المحاولة ويجمع المقاييس، كما يصفها توثيق Istio.
- الخلفية للواجهة Backend for Frontend (BFF): خدمة صغيرة لكل نوع من العملاء، مثل خلفية لتطبيق الجوال وأخرى للويب، تجمع البيانات من عدة خدمات وتعيدها بالشكل الذي تحتاجه تلك الشاشة بالضبط، ويذكر Sam Newman في شرح النمط أنها تكون غالباً بيد نفس الفريق الذي يبني الواجهة.
والصورة التالية تبين مكان كل واحد منها:

والجدول التالي يلخص الفروق:
| Reverse Proxy | موزع الحمل | API Gateway | Service Mesh | BFF | |
|---|---|---|---|---|---|
| أين يقف | الحافة | أي طبقة | الحافة | بين الخدمات | الحافة، خلف البوابة غالباً |
| يعرف العميل؟ | لا | لا | نعم، بالمفتاح أو التوكن | يعرف الخدمة المتصلة (هوية mTLS) | نعم، ومعه جلسة المستخدم |
| مهمته الأساسية | TLS والتوجيه بالنطاق والمسار | توزيع الحمل وإخراج النسخ المعطلة | سياسات لكل عميل ومسار: المصادقة والحصص والإصدارات | أمان وموثوقية الاتصال الداخلي | تجميع البيانات بشكل يناسب شاشة معينة |
| فيه منطق عمل؟ | لا | لا | لا يجب | لا | نعم، منطق العرض فقط |
| من يملكه عادة | فريق البنية | فريق البنية | فريق المنصة | فريق المنصة | فريق الواجهة |
| أمثلة | Nginx، Caddy، Traefik | HAProxy، Keepalived، موزعات السحابة | Apache APISIX، Kong، KrakenD، Tyk | Istio، Linkerd | خدمة Node.js أو Go تكتبها أنت |
ولاحظ أن البوابة الحديثة تقوم غالباً بعمل ال Reverse Proxy وموزع الحمل أيضاً، فهي تنهي TLS وتوزع على نسخ الخدمة وتفحص صحتها، ولهذا لا تحتاج في أغلب الحالات إلى الثلاثة معاً، وإنما إلى بوابة واحدة وربما Reverse Proxy أمامها للمواقع العادية.
وإذا أردت أن ترى بماذا تقرر كل طبقة من ال DNS إلى التطبيق، ومتى تضيف كل منها، مع تجربة تضع موزع الطبقة الرابعة وال Reverse Proxy والبوابة في صف واحد وتقيس ما تضيفه كل منها، فاقرأ مقال Reverse Proxy أم Load Balancer أم API Gateway.
ما الذي تتولاه البوابة؟
الصورة التالية تبين الخطوات التي يمر بها طلب واحد داخل البوابة، وكل خطوة منها يمكن أن ترفض الطلب قبل أن يصل إلى خدمتك:

التوجيه Routing والإصدارات Versioning
أول عمل للبوابة أن تحول العنوان العام إلى خدمة داخلية: /v1/orders/* إلى خدمة الطلبات، و/v1/users/* إلى خدمة المستخدمين، وبهذا يرى العميل API واحداً مهما كان عدد الخدمات خلفه، وتستطيع أن تقسم خدمة إلى اثنتين أو تنقل مساراً من النظام القديم إلى خدمة جديدة دون أن يعرف العميل، وهذا هو أساس نمط الخنق Strangler Fig الذي يستبدل النظام القديم مساراً بعد مسار. والبوابة أيضاً هي المكان الطبيعي لاختيار الإصدار، سواء كان في المسار /v2/ أو في ترويسة، وقد ناقشنا اختيار طريقة الإصدار نفسها في تصميم واجهات API، وما تضيفه البوابة هو أنها تستطيع إرسال نسبة صغيرة من الطلبات إلى الإصدار الجديد قبل أن تنقل الجميع، وهو ما يسمى الإطلاق التدريجي Canary Release.
المصادقة Authentication في البوابة، والصلاحيات Authorization في الخدمة
البوابة تجيب عن سؤال «من أنت؟»، فتتحقق من مفتاح ال API أو من توقيع ال JWT وتاريخ انتهائه، أو تتحقق من التوكن عبر مزود الهوية بال OpenID Connect، وترفض كل طلب لا يثبت هويته برمز 401 قبل أن يصل إلى أي خدمة، ثم تضيف إلى الطلب ترويسة فيها اسم العميل لتعرفه الخدمة. وقد شرحنا بنية ال JWT وكيف يتحقق منه في الجلسات وJWT، وتدفقات OAuth في شرح OAuth وOpenID Connect، وهذه هي الأنواع الثلاثة التي تراها في البوابات:
- مفاتيح ال API Keys: سلسلة ثابتة لكل عميل، مناسبة للشركاء والسيرفرات، وسهلة الإلغاء لأنها محفوظة في البوابة.
- JWT تصدره أنت: البوابة تتحقق من التوقيع بمفتاح سري مشترك أو بمفتاح عام، دون أن تسأل أحداً.
- OpenID Connect: البوابة تقرأ مفاتيح مزود الهوية من رابط JWKS وتتحقق من التوكن، أو تحول المتصفح إلى صفحة الدخول ثم تحفظ جلسة له.
أما سؤال «هل يحق لك هذا؟» فلا تستطيع البوابة الإجابة عنه في أغلب الحالات، والسبب أن الإجابة تحتاج إلى بيانات لا تملكها، فالبوابة لا تعرف أن الطلب 17 يخص مستخدماً آخر، ولهذا تبقى الصلاحيات في الخدمة أو في محرك صلاحيات تسأله الخدمة، كما شرحنا في أين يكون الفحص: البوابة أم التطبيق؟. ولا تقم بإزالة التحقق من الخدمة لأن «البوابة تتحقق»، والسبب أن أي طلب يصل إلى الخدمة من داخل الشبكة دون أن يمر بالبوابة سوف يقبل دون سؤال.
الحدود Rate Limits والحصص Quotas
البوابة تعرف العميل، لذلك تستطيع أن تحد كل عميل على حدة: عشرة طلبات في الثانية لتطبيق الجوال، وألف طلب في اليوم للشريك المجاني، وعشرة آلاف للشريك المدفوع، وترد برمز 429 على الزائد. والفرق بين الحد والحصة أن الحد يحمي السيرفر من الكثرة في وقت قصير، والحصة تتعلق بالعقد التجاري على مدة أطول مثل يوم أو شهر. وقد شرحنا الخوارزميات وطريقة الحد على عدة سيرفرات في تحديد معدل الطلبات، والذي يجب أن تتذكره هنا أن البوابة التي تعمل بعدة نسخ تحتاج إلى عداد مشترك مثل Redis إذا أردت حداً دقيقاً على مستوى النظام كله، وإلا فكل نسخة تعد لنفسها.
تحويل الطلب والرد Transformation
البوابة تستطيع أن تضيف ترويسة أو تحذفها، وأن تعيد كتابة المسار، وأن تحذف من الرد حقلاً داخلياً لا يجب أن يراه العميل، وأن تحول طلب REST إلى gRPC. وهذا مفيد بقدر صغير، مثل حذف ترويسة Authorization قبل أن تصل إلى خدمة لا تحتاجها، أو إضافة X-Request-Id لكل طلب، ولكنه يصبح خطراً عندما تبدأ البوابة بتعديل محتوى الردود بحسب قواعد العمل، كما سيأتي في الأخطاء.
التخزين المؤقت Caching
بعض البوابات تحفظ ردود طلبات GET لمدة قصيرة فترد على الطلب المتكرر دون أن تسأل الخدمة، وهذا يناسب البيانات العامة مثل قائمة المنتجات أو أسعار الصرف، ولا يناسب أي رد يختلف من مستخدم إلى آخر إلا إذا كان المستخدم جزءاً من مفتاح التخزين. وقد شرحنا متى يكون التخزين المؤقت آمناً وكيف تبطله في التخزين المؤقت، والقاعدة نفسها تنطبق هنا.
CORS
عندما يتصل موقع على نطاق آخر بال API من المتصفح يحتاج الرد إلى ترويسات CORS، ووضعها في البوابة يضمن أن كل الخدمات ترد بنفس القائمة من المصادر المسموحة، بدل أن تنسى خدمة واحدة الطلب التمهيدي Preflight أو ترد بالنجمة. وقد شرحنا CORS وأخطاءها في شرح CORS، وتذكر أن CORS لا تحمي ال API من أي شيء خارج المتصفح.
المهلات وإعادة المحاولة وقاطع الدائرة
البوابة هي آخر من يتحدث مع الخدمة قبل العميل، لذلك تحتاج إلى مهلة Timeout لكل خدمة، وإلى فحص صحة يخرج النسخة المعطلة من التوزيع، وإلى قاطع دائرة Circuit Breaker يتوقف عن إرسال الطلبات إلى خدمة تفشل. ولكن انتبه إلى إعادة المحاولة Retry، فإذا كانت البوابة تعيد المحاولة والخدمة تعيد المحاولة والكلاينت يعيد المحاولة فإن الطلب الواحد يتحول إلى عشرات الطلبات على الخدمة المتعبة، وقد رأينا ذلك بالأرقام في كيف تمنع خدمة بطيئة من إسقاط نظامك كله، والأنسب أن تعيد البوابة المحاولة على طلبات GET فقط وعلى نسخة أخرى ومرة واحدة.
المراقبة: سجلات ومقاييس وتتبع
لأن كل طلب يمر بالبوابة فهي أفضل مكان لصورة كاملة عن ال API: عدد الطلبات لكل مسار ولكل عميل، ونسبة الأخطاء، وزمن الاستجابة p99، وكل ذلك دون أن تضيف سطراً في الخدمات. والبوابة أيضاً تبدأ ال Trace، فتضيف ترويسة traceparent إلى الطلب فتكمل الخدمات نفس ال Trace، وقد شرحنا ذلك في التتبع الموزع، وسوف ترى هذه الترويسة تصل إلى الخدمة في دليل APISIX العملي.
بوابة المطورين Developer Portal وإدارة المفاتيح
عندما يكون لل API عملاء من خارج شركتك تحتاج إلى مكان يسجل فيه المطور، ويقرأ التوثيق المبني من ملف OpenAPI، ويصدر مفتاحه ويلغيه، ويرى استهلاكه من الحصة. وهذا الجزء هو أكثر ما يختلف بين النسخ المفتوحة والمدفوعة من أدوات البوابات، فأغلب النسخ المفتوحة تعطيك البوابة نفسها ومفاتيح تديرها عبر Admin API، أما بوابة المطورين بواجهة تسجيل ذاتي فتأتي غالباً في النسخ التجارية، وسوف نفصل ذلك لكل أداة في مقارنة أدوات البوابات.
تجربة عملية: Nginx كأصغر بوابة ممكنة
قبل أن نختار أداة بوابة كاملة سوف نبني بوابة بأداة يعرفها الجميع، وهي Nginx، لنرى ما الذي نحصل عليه بإعداد قصير وأين يتوقف. والتجربة فيها خدمتان صغيرتان orders وusers خلف Nginx واحد، وكل ال Containers تبدأ أسماؤها بـ lab-gw-c-، والمنفذ الوحيد المنشور هو 19280 على 127.0.0.1.
ملفات التجربة
الخدمة الخلفية ترد باسمها والمسار الذي وصلها وقيمة الترويسة X-Client التي تضيفها البوابة، وهذا ملف backend.conf:
# One small backend that tells us its name and what the gateway sent it.
server {
listen 8080;
default_type application/json;
location / { return 200 '{"service":"$hostname","path":"$uri","client":"$http_x_client"}\n'; }
}والبوابة نفسها في ملف gateway.conf:
# Nginx as the smallest possible API gateway: routing, API keys, a limit per client, and a header for the service.
map $http_x_api_key $client {
default "";
"k-7d1f0c9a2b4e6f8a" "mobile-app";
"k-3a5c7e9b1d2f4a6c" "partner-acme";
}
limit_req_zone $client zone=per_client:10m rate=5r/s;
upstream orders { server lab-gw-c-orders:8080; keepalive 16; }
upstream users { server lab-gw-c-users:8080; keepalive 16; }
server {
listen 8080;
default_type application/json;
# every API path needs a known key
if ($client = "") { return 401 '{"error":"missing or unknown API key"}\n'; }
limit_req zone=per_client burst=5 nodelay;
limit_req_status 429;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Client $client; # the service learns who called, not the key itself
proxy_set_header X-Api-Key "";
location /v1/orders/ { proxy_pass http://orders/; }
location /v1/users/ { proxy_pass http://users/; }
location / { return 404 '{"error":"no such route"}\n'; }
}في الإعداد أعلاه لاحظ التالي:
- الأمر map يحول قيمة الترويسة
X-Api-Keyإلى اسم العميل، وأي مفتاح غير موجود في القائمة يعطي قيمة فارغة فيرفض الطلب برمز 401. - الحد في limit_req مفتاحه اسم العميل وليس عنوان IP، فلكل عميل خمسة طلبات في الثانية ودفعة Burst من خمسة، والزائد يرفض برمز 429 بدل الرمز الافتراضي 503.
- البوابة تحذف المفتاح نفسه قبل أن ترسل الطلب، وتضيف بدلاً منه
X-Client، فالخدمة تعرف من المتصل دون أن ترى سره. - المسار
/v1/orders/يتحول إلى/في الخدمة، فالخدمة لا تعرف شيئاً عن رقم الإصدار العام.
وملف compose.yaml:
# The concepts lab: Nginx as a minimal gateway in front of two services.
name: lab-gw-concepts
services:
gateway:
image: nginx:1.31.6-alpine
container_name: lab-gw-c-nginx
volumes: ["./gateway.conf:/etc/nginx/conf.d/default.conf:ro"]
ports: ["127.0.0.1:19280:8080"]
depends_on: [orders, users]
orders: { image: nginx:1.31.6-alpine, container_name: lab-gw-c-orders, hostname: orders, volumes: ["./backend.conf:/etc/nginx/conf.d/default.conf:ro"] }
users: { image: nginx:1.31.6-alpine, container_name: lab-gw-c-users, hostname: users, volumes: ["./backend.conf:/etc/nginx/conf.d/default.conf:ro"] }التشغيل والطلبات الأولى
docker compose up -d
curl -i http://127.0.0.1:19280/v1/orders/17
curl http://127.0.0.1:19280/v1/orders/17 -H "X-Api-Key: k-7d1f0c9a2b4e6f8a"
curl http://127.0.0.1:19280/v1/users/me -H "X-Api-Key: k-3a5c7e9b1d2f4a6c"والمخرج سوف يكون كما يلي:
HTTP/1.1 401 Unauthorized
{"error":"missing or unknown API key"}
{"service":"orders","path":"/17","client":"mobile-app"}
{"service":"users","path":"/me","client":"partner-acme"}الطلب الأول بلا مفتاح لم يصل إلى أي خدمة، والطلبان التاليان وصلا إلى خدمتين مختلفتين عبر عنوان واحد، وكل خدمة عرفت اسم العميل من الترويسة.
نكسرها: عميل واحد يرسل عشرين طلباً في نفس اللحظة
الآن سوف نرسل عشرين طلباً متوازياً بمفتاح تطبيق الجوال، ثم طلباً واحداً من الشريك في نفس الوقت:
curl -s --parallel --parallel-immediate --parallel-max 20 -H "X-Api-Key: k-7d1f0c9a2b4e6f8a" -w '%{http_code} ' \
$(for i in $(seq 20); do printf -- '-o /dev/null http://127.0.0.1:19280/v1/orders/1 '; done) | tr ' ' '\n' | sort | uniq -c
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:19280/v1/users/me -H "X-Api-Key: k-3a5c7e9b1d2f4a6c"والمخرج سوف يكون كما يلي:
6 200
14 429
200مر ستة طلبات، أي طلب الثانية الحالية ومعه الدفعة الخمسة، ورفض الأربعة عشر الباقية برمز 429، بينما مر طلب الشريك لأن له عداداً خاصاً به، وهذا هو الفرق بين حد لكل عميل وحد عام على السيرفر كله.
ثم نرسل JWT بدل المفتاح، كما يفعل تطبيق يدخل المستخدم عبر مزود هوية:
curl -i http://127.0.0.1:19280/v1/orders/17 -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSJ9.x"HTTP/1.1 401 Unauthorized
{"error":"missing or unknown API key"}والبوابة لا تعرف ما هو ال JWT أصلاً، والسبب أن وحدة auth_jwt في Nginx متاحة فقط ضمن الاشتراك التجاري كما يذكر توثيقها، فإذا أردت التحقق من JWT في Nginx المفتوح فعليك أن تكتب خدمة تحقق وتربطها بالأمر auth_request، أو أن تضيف وحدة Lua، وفي هذه اللحظة تكون قد بدأت تبني بوابة بنفسك.
أين يتوقف Nginx؟
هذه البوابة الصغيرة تكفي فعلاً لفريق عنده عميلان أو ثلاثة وخدمات قليلة، ولكن لاحظ ما ينقصها عندما يكبر العدد:
- إضافة عميل أو إلغاء مفتاح مسروق تعني تعديل ملف الإعداد وإعادة تحميل Nginx على كل نسخة، ولا توجد واجهة أو API تضيف المفتاح وهو يعمل.
- العداد في الذاكرة المشتركة لكل نسخة على حدة، فإذا شغلت ثلاث نسخ من البوابة أصبح الحد الفعلي ثلاثة أضعاف، ولا توجد حصة يومية أو شهرية.
- لا يوجد تحقق من JWT أو OpenID Connect في النسخة المفتوحة كما رأينا.
- لا توجد مقاييس لكل عميل ولكل مسار إلا بتحليل السجلات بنفسك، ولا توجد بوابة مطورين.
- كل تغيير في مسار يحتاج إلى من يعرف صيغة Nginx، فلا تستطيع أن تعطي كل فريق مساراته.
وعندما تجد نفسك تكتب حلولاً لثلاثة من هذه البنود فقد حان وقت أداة بوابة حقيقية، وهذا موضوع مقارنة الأدوات.
إزالة التجربة
docker compose downهذا الأمر يحذف ال Containers الثلاثة وشبكة المشروع lab-gw-concepts_default فقط، ولا يلمس أي شيء آخر على جهازك.
أين تضع البوابة؟ الأشكال الشائعة
الصورة التالية تبين ثلاثة أشكال تراها في الشركات:

بوابة واحدة أم بوابة لكل فريق؟
البوابة الواحدة هي البداية الصحيحة لأغلب الشركات، فمكان واحد للسياسات وللشهادات وللمراقبة، وفريق واحد يعرفها. ولكن عندما يصبح عندك عشرة فرق فإن كل فريق يحتاج إلى تغيير مساراته عدة مرات في الأسبوع، وإذا كانت كل هذه التغييرات تمر عبر تذكرة إلى فريق المنصة فسوف تصبح البوابة طابوراً ينتظر فيه الجميع. والحل الشائع أن تبقى حافة مشتركة رفيعة تتولى TLS والحماية العامة، وخلفها بوابة لكل فريق أو لكل مجال يملك الفريق مساراتها وإضافاتها، أو أن تبقى بوابة واحدة ولكن إعدادها في Git مقسم إلى ملفات لكل فريق مع مراجعة تلقائية، فلا يحتاج الفريق إلى إذن أحد لتغيير مساراته.
نمط الخلفية للواجهة BFF
تطبيق الجوال يعرض في الشاشة الرئيسية الطلبات الأخيرة ورصيد المحفظة وعدد الإشعارات، فإذا طلب كل واحد منها من خدمة مختلفة فهذه ثلاثة طلبات عبر شبكة جوال بطيئة، والحل أن تجمعها خدمة واحدة قريبة من الخدمات وترد بشكل واحد يناسب هذه الشاشة. وهذه هي ال BFF، ويذكر Sam Newman أنها انتشرت في SoundCloud، وأن القاعدة المقترحة خلفية لكل تجربة استخدام، فإذا كان تطبيق iOS وتطبيق Android متشابهين فخلفية واحدة تكفيهما. ولاحظ أن ال BFF ليست بديلاً عن البوابة، فالترتيب المعتاد أن البوابة في الأمام تتحقق من الهوية وتطبق الحدود، وخلفها ال BFF يجمع البيانات، وله فائدة أمنية أيضاً لتطبيقات الصفحة الواحدة، فهو يحفظ توكنات OAuth عنده ويعطي المتصفح ملف جلسة فقط، كما شرحنا في أين تحفظ ما يثبت الدخول في المتصفح.
بوابة للحركة الخارجية، وأخرى للداخلية؟
الحركة من الإنترنت تحتاج إلى كل شيء: TLS عام، ومفاتيح عملاء، وحصص، وحماية من الإساءة. أما الحركة بين خدماتك فتحتاج إلى أشياء أخرى: هوية الخدمة المتصلة، وتشفير داخلي، وإعادة محاولة ومهلات. لذلك لا تقم بتمرير الحركة الداخلية عبر البوابة الخارجية، والسبب أنك تضيف قفزة شبكة وتجعل البوابة نقطة فشل لكل اتصال داخلي، فإذا احتجت سياسات بين الخدمات فهذا مكان Service Mesh أو بوابة داخلية منفصلة لها إعدادها الخاص، وإذا كانت خدماتك قليلة فيكفي ما شرحناه في اكتشاف الخدمات مع مكتبة مهلات جيدة.
واجهة Gateway API في Kubernetes
في Kubernetes كان مورد Ingress هو الطريقة المعتادة لإدخال الحركة إلى العنقود، ولكنه محدود، فكل ما يتجاوز التوجيه البسيط يكتب في Annotations تختلف من أداة إلى أخرى. لذلك أنشأت مجموعة SIG Network واجهة Gateway API، ووصلت مواردها الأساسية GatewayClass وGateway وHTTPRoute إلى الإصدار المستقر في الإصدار v1.0 في أكتوبر 2023، ثم لحق بها GRPCRoute في الإصدار v1.1 وTLSRoute في الإصدار v1.5، بينما يبقى TCPRoute وUDPRoute في القناة التجريبية بحسب صفحة الموارد، وآخر إصدار مستقر عند كتابة المقال هو v1.6.3، ويجري تجهيز v1.7 كما في صفحة الإصدارات. وفكرتها الأساسية أنها تقسم الإعداد بحسب من يملكه، كما تبين الصورة التالية:

وهذا التقسيم يحل مشكلة «بوابة واحدة لفريق واحد» التي ذكرناها، فالمطور يكتب HTTPRoute في مساحة الأسماء Namespace الخاصة به دون أن يلمس إعداد البوابة، ومدير العنقود يحدد في ال Gateway أي مساحات يسمح لها بالربط. ويجدر الانتباه إلى خبر مهم لمن يستخدم Kubernetes: أعلنت SIG Network إيقاف مشروع ingress-nginx مع صيانة بأفضل جهد حتى مارس 2026 ثم لا إصدارات ولا إصلاحات أمنية بعدها، مع التأكيد أن مورد Ingress نفسه لم يلغ، وأن الطريق المقترح هو Gateway API. وأغلب أدوات البوابات اليوم تنفذ Gateway API، والقائمة الكاملة في صفحة التنفيذات.
أخطاء تحول البوابة إلى مشكلة
منطق العمل داخل البوابة
تبدأ القصة بإضافة صغيرة تحسب خصماً للعملاء المميزين لأن «البوابة تعرف العميل»، ثم إضافة تمنع الطلب إذا كان المخزون صفراً، وبعد سنة تجد أن جزءاً من قواعد عملك مكتوب بلغة Lua داخل إعداد البوابة، لا يختبره أحد، ولا يعرف فريق الخدمة أنه موجود. والقاعدة أن البوابة تتخذ قرارات عن الطلب نفسه، مثل الهوية والحجم والمعدل والمسار، ولا تتخذ قرارات عن معنى البيانات، والسبب أن قرارات المعنى تتغير مع العمل وتحتاج إلى اختبارات ومراجعة كباقي الكود، ومكانها الخدمة أو ال BFF.
نقطة فشل واحدة Single Point of Failure
كل الطلبات تمر بالبوابة، فإذا توقفت توقف كل شيء، ولهذا شغل نسختين على الأقل خلف موزع حمل أو عنوان IP عائم كما في Keepalived، واجعل البوابة بلا حالة قدر الإمكان، فيكون إعدادها في مخزن مستقل أو في ملفات، وعداداتها المشتركة في Redis. وتذكر أن البوابة تعتمد على أشياء أخرى قد تسقطها معها، مثل مخزن الإعداد ومزود الهوية، فاسأل نفسك: ماذا يحدث للطلبات إذا توقف etcd أو توقف authentik؟ وسوف نجرب الحالة الأولى فعلاً في دليل APISIX.
كل شيء يمر عبر فريق واحد
إذا كان كل مسار جديد يحتاج إلى تذكرة لفريق المنصة فسوف يجد الفريق طريقاً حول البوابة، مثل نطاق فرعي يذهب مباشرة إلى خدمته، وهذا أسوأ من عدم وجود بوابة لأن الجميع يظن أن السياسات مطبقة. والحل أن يكون إعداد البوابة كوداً في Git، ولكل فريق ملفاته، ومعها فحص تلقائي يمنع الأخطاء الخطيرة مثل مسار بلا مصادقة.
أخطاء أخرى
- الاعتماد على البوابة وحدها للصلاحيات، وترك الخدمة تقبل أي طلب يصلها من داخل الشبكة.
- إعادة المحاولة في البوابة وفي الخدمة وفي الكلاينت معاً.
- تخزين مؤقت لردود تختلف من مستخدم إلى آخر دون أن يكون المستخدم في مفتاح التخزين.
- Admin API للبوابة منشور على الإنترنت أو محمي بالمفتاح الافتراضي.
- حد واحد للجميع بدل حد لكل عميل، فيعاقب العملاء الهادئون بسبب عميل واحد.
متى لا تحتاج إلى بوابة؟
إذا كان عندك تطبيق واحد Monolith يخدم موقعك وتطبيقك، فالتطبيق نفسه يتحقق من الجلسة ويطبق الحدود بمكتبة جيدة، وأمامه Reverse Proxy للشهادات، ولا تضيف البوابة إلا قفزة أخرى وبرنامجاً آخر تحدثه وتراقبه، وقد ناقشنا متى يستحق تقسيم التطبيق أصلاً في Monolith أم Microservices؟. وكذلك إذا كانت خدماتك داخلية فقط ولا يتصل بها إلا تطبيقاتك أنت. والعلامات التي تقول إنك تحتاج إلى بوابة هي:
- ثلاث خدمات أو أكثر يصل إليها العملاء مباشرة، وكل منها يكرر المصادقة والحدود.
- عملاء من خارج شركتك بمفاتيح وحصص مختلفة، وتحتاج إلى إلغاء مفتاح خلال دقيقة.
- تريد نقل مسارات من نظام قديم إلى خدمات جديدة تدريجياً دون أن يغير العملاء شيئاً.
- تحتاج إلى مقاييس موحدة لكل مسار ولكل عميل، ولا يمكنك إضافتها في كل خدمة.
وإذا قررت أنك تحتاجها فلا تبدأ بأكبر أداة، وإنما بأبسط واحدة تغطي هذه العلامات، وهذا ما سوف نقيسه في المقال التالي.
دليلKong أم APISIX أم Tyk أم KrakenD؟ أدوات بوابات APIما المفتوح فعلاً في كل أداة بوابة API، وكيف تختلف في حفظ الإعداد، وأرقام من تجربة واحدة على نفس الجهاز.
دليلApache APISIX عملياً: من أول Route إلى بوابة إنتاجكيف تشغل Apache APISIX مع etcd، وتحمي واجهاتك بالتوكنات والحدود وauthentik، وتراقبها وتأخذ نسخة احتياطية من إعدادها.الخلاصة
- بوابة ال API Gateway هي Reverse Proxy يعرف العميل، فتطبق السياسات لكل عميل ولكل مسار في مكان واحد بدل أن تكررها كل خدمة.
- البوابة تتحقق من الهوية وترفض بـ 401 و429 قبل أن يصل الطلب إلى الخدمة، أما الصلاحيات التي تحتاج إلى البيانات فتبقى في الخدمة.
- Nginx يعطيك بوابة صغيرة بمفاتيح وحد لكل عميل في عشرين سطراً، ولكنه لا يتحقق من JWT في النسخة المفتوحة، ولا يدير المفاتيح وهو يعمل، ولا يشارك العدادات بين النسخ.
- ابدأ ببوابة واحدة إعدادها في Git، وانتقل إلى بوابة لكل فريق أو إلى Gateway API في Kubernetes عندما يصبح فريق المنصة طابوراً.
- لا تضع منطق العمل في البوابة، وشغلها بنسختين على الأقل، ولا تمرر الحركة الداخلية عبرها.
- إذا كان عندك تطبيق واحد وعملاء من شركتك فقط فأنت لا تحتاج إلى بوابة بعد.
سجل التحديثات
- أكتوبر 2026: كتابة المقال، وتنفيذ التجربة العملية على Docker Engine 29.8.0 مع nginx:1.31.6-alpine.
- أكتوبر 2026: إضافة رابط إلى مقال المقارنة بين ال Reverse Proxy وموزع الحمل وبوابة ال API في نهاية قسم البوابة وأقاربها.