الأدلة التقنية

ما هي بوابة API Gateway ومتى تحتاجها؟

ما الذي تفعله بوابة API ولا يفعله ال Reverse Proxy، وما الذي يبقى في خدماتك، ومتى لا تحتاج إلى بوابة أصلاً.

ما هي بوابة API Gateway ومتى تحتاجها؟

في دليل أمن واجهات 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 البوابة بأنها نقطة الدخول الوحيدة لكل العملاء، وأنها قد تعطي كل نوع من العملاء واجهة مختلفة.

والصورة التالية تبين الفرق بين نظام بلا بوابة ونظام فيه بوابة:

لوحتان. في الأعلى بخلفية حمراء فاتحة: تطبيق جوال وتطبيق ويب وشريك يتصل كل منهم مباشرة بثلاث خدمات orders وusers وpayments، فتظهر تسعة خطوط متقاطعة، وبجانب كل خدمة أربع علامات حمراء auth وlimits وCORS وlogs، وتحتها: 9 paths, 3 copies of the same checks, 3 different bugs. وفي الأسفل بخلفية خضراء: نفس العملاء يتصلون ببوابة API gateway واحدة فوقها علامات TLS وauth وrate limit وCORS وlogs، ومنها أسهم إلى الخدمات الثلاث، وبجانب كل خدمة علامة زرقاء business rules + authorization
دون بوابة تكرر كل خدمة نفس الفحوصات، ومع البوابة تبقى في الخدمة قواعد العمل والصلاحيات فقط

وقد يتساءل البعض: لماذا لا نكتب هذه الفحوصات في مكتبة مشتركة تستخدمها كل الخدمات؟ والإجابة أن المكتبة حل جيد عندما تكون كل الخدمات بلغة واحدة ويحدثها فريق واحد، ولكن عندما تكون خدمة بلغة 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 في شرح النمط أنها تكون غالباً بيد نفس الفريق الذي يبني الواجهة.

والصورة التالية تبين مكان كل واحد منها:

منطقتان. منطقة الحافة EDGE (NORTH-SOUTH) فيها reverse proxy مكتوب تحته TLS, hosts, paths، وAPI gateway تحته keys, JWT وquotas, versions، وBFF تحته one per client app وowned by the UI team، وتصلها أسهم من جوال وويب وشريك. ثم منطقة INSIDE (EAST-WEST) فيها load balancer يوزع إلى ثلاث خدمات orders وusers وpayments، وبجانب كل خدمة شكل سداسي أخضر صغير، وخطوط متقطعة بينها مكتوب بجانبها service mesh: a sidecar next to each service: mTLS, retries, metrics
البوابة وال Reverse Proxy وال BFF على الحافة، وال Service Mesh بين الخدمات من الداخل

والجدول التالي يلخص الفروق:

Reverse Proxyموزع الحملAPI GatewayService MeshBFF
أين يقفالحافةأي طبقةالحافةبين الخدماتالحافة، خلف البوابة غالباً
يعرف العميل؟لالانعم، بالمفتاح أو التوكنيعرف الخدمة المتصلة (هوية mTLS)نعم، ومعه جلسة المستخدم
مهمته الأساسيةTLS والتوجيه بالنطاق والمسارتوزيع الحمل وإخراج النسخ المعطلةسياسات لكل عميل ومسار: المصادقة والحصص والإصداراتأمان وموثوقية الاتصال الداخليتجميع البيانات بشكل يناسب شاشة معينة
فيه منطق عمل؟لالالا يجبلانعم، منطق العرض فقط
من يملكه عادةفريق البنيةفريق البنيةفريق المنصةفريق المنصةفريق الواجهة
أمثلةNginx، Caddy، TraefikHAProxy، Keepalived، موزعات السحابةApache APISIX، Kong، KrakenD، TykIstio، Linkerdخدمة Node.js أو Go تكتبها أنت

ولاحظ أن البوابة الحديثة تقوم غالباً بعمل ال Reverse Proxy وموزع الحمل أيضاً، فهي تنهي TLS وتوزع على نسخ الخدمة وتفحص صحتها، ولهذا لا تحتاج في أغلب الحالات إلى الثلاثة معاً، وإنما إلى بوابة واحدة وربما Reverse Proxy أمامها للمواقع العادية.

وإذا أردت أن ترى بماذا تقرر كل طبقة من ال DNS إلى التطبيق، ومتى تضيف كل منها، مع تجربة تضع موزع الطبقة الرابعة وال Reverse Proxy والبوابة في صف واحد وتقيس ما تضيفه كل منها، فاقرأ مقال Reverse Proxy أم Load Balancer أم API Gateway.

ما الذي تتولاه البوابة؟

الصورة التالية تبين الخطوات التي يمر بها طلب واحد داخل البوابة، وكل خطوة منها يمكن أن ترفض الطلب قبل أن يصل إلى خدمتك:

جوال يرسل GET /v1/orders/17 مع Bearer eyJ إلى مستطيل أخضر API GATEWAY فيه ست خطوات مرقمة: 1 TLS decrypt، 2 route path, version، 3 authenticate JWT, API key، 4 rate limit per client، 5 transform headers, CORS، 6 proxy timeout, retry. ومن الخطوة الثالثة سهم أحمر إلى 401 ومن الرابعة إلى 429 وتحتهما rejected here, the service never sees these requests، وبجانبها a log line, metrics and a trace span. ثم سهم إلى orders service وتحتها is order 17 yours? وauthorization stays here، وفي الأسفل الترويسات X-Consumer-Username: mobile_app وtraceparent
الطلب يمر بست خطوات داخل البوابة، وسؤال «هل هذا الطلب يخصك؟» يبقى في الخدمة

التوجيه 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 فقط، ولا يلمس أي شيء آخر على جهازك.

أين تضع البوابة؟ الأشكال الشائعة

الصورة التالية تبين ثلاثة أشكال تراها في الشركات:

ثلاثة أعمدة. ONE GATEWAY: جوال ومتصفح إلى بوابة واحدة ثم إلى orders وusers وbilling، وتحتها one team runs it, every change waits for them. A GATEWAY PER TEAM: shared edge يوزع إلى بوابتين، بوابة team A وبوابة team B ولكل منهما خدمتان داخل مربع متقطع، وتحتها each team owns its routes and its plugins. A BFF PER CLIENT: جوال يتصل بـ mobile BFF ومتصفح يتصل بـ web BFF، وكلاهما يتصل بالخدمات الثلاث، وتحتها each client app gets the data shape it needs
بوابة واحدة، أو بوابة لكل فريق خلف حافة مشتركة، أو خلفية لكل نوع من العملاء

بوابة واحدة أم بوابة لكل فريق؟

البوابة الواحدة هي البداية الصحيحة لأغلب الشركات، فمكان واحد للسياسات وللشهادات وللمراقبة، وفريق واحد يعرفها. ولكن عندما يصبح عندك عشرة فرق فإن كل فريق يحتاج إلى تغيير مساراته عدة مرات في الأسبوع، وإذا كانت كل هذه التغييرات تمر عبر تذكرة إلى فريق المنصة فسوف تصبح البوابة طابوراً ينتظر فيه الجميع. والحل الشائع أن تبقى حافة مشتركة رفيعة تتولى 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 كما في صفحة الإصدارات. وفكرتها الأساسية أنها تقسم الإعداد بحسب من يملكه، كما تبين الصورة التالية:

ثلاثة صفوف، في كل صف شخص يشير إلى مورد. infrastructure provider إلى GatewayClass وبجانبها which implementation runs it: Envoy Gateway, APISIX, Kong, Traefik. cluster operator إلى Gateway وبجانبها listeners: port 443, hostnames, the certificate, which namespaces may attach. application developer إلى HTTPRoute وبجانبها paths and headers, weights 90/10 between two Services، وسهم إلى Services. وأسهم بين الموارد references وattaches to، وفي الأسفل GA since v1.0 (Oct 2023): GatewayClass, Gateway, HTTPRoute. GRPCRoute since v1.1, TLSRoute since v1.5
مزود البنية يختار التنفيذ، ومدير العنقود يملك ال Gateway، والمطور يملك مساراته في HTTPRoute

وهذا التقسيم يحل مشكلة «بوابة واحدة لفريق واحد» التي ذكرناها، فالمطور يكتب 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 في نهاية قسم البوابة وأقاربها.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.