0Pricing
PHP Academy · درس

بوابات API واكتشاف الخدمات

وجّه الخدمات واجمعها وحدّد مواقعها ديناميكيًا

بوابات API واكتشاف الخدمات درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.

البوابات واكتشاف الخدمات

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

يغطي هذا الدرس الأمرين، باستخدام PHP عند حافة البوابة.

ما الذي تفعله بوابة API

بوابة API هي نقطة دخول واحدة أمام خدماتكم. وتشمل مسؤولياتها المعتادة:

  • توجيه الطلبات إلى الواجهة الخلفية الصحيحة.
  • الاهتمامات المشتركة: المصادقة، وتحديد معدل الطلبات، وCORS، وإنهاء TLS.
  • التجميع: دمج عدة استدعاءات للواجهات الخلفية في استجابة واحدة للعميل.
  • ترجمة البروتوكولات: استقبال REST خارجيًا وإرسال gRPC داخليًا.

تحافظ على بساطة العملاء وتُركّز السياسات التي كنتم ستكررونها بخلاف ذلك في كل خدمة.

التوجيه عند الحافة

في جوهرها، تربط البوابة مسارًا واردًا بخدمة upstream. تنفذ بوابات الإنتاج (Kong, Traefik, Nginx, AWS API Gateway) ذلك بطريقة وصفية، لكن المنطق بسيط بما يكفي لتوضيحه في PHP.

<?php
$routes = [
    '#^/api/orders#'    => 'http://orders-svc',
    '#^/api/customers#' => 'http://customers-svc',
    '#^/api/catalog#'   => 'http://catalog-svc',
];

function resolveUpstream(string $path, array $routes): ?string {
    foreach ($routes as $pattern => $upstream) {
        if (preg_match($pattern, $path)) {
            return $upstream . $path;
        }
    }
    return null; // 404 at the gateway
}

echo resolveUpstream('/api/orders/42', $routes), "\n";

توحيد المصادقة

تحققوا من هوية المستدعي مرة واحدة عند البوابة، ثم مرروا هوية موثوقة إلى الخدمات اللاحقة، بحيث لا تضطر كل خدمة إلى إعادة التحقق من الرمز الخام. تتحقق البوابة من توقيع JWT وانتهائه، وتضيف ترويسات مثل X-User-Id إلى الطلب الداخلي (عبر شبكة موثوقة).

<?php
function authenticate(string $authHeader): ?array {
    if (!str_starts_with($authHeader, 'Bearer ')) return null;
    $jwt = substr($authHeader, 7);
    $claims = verifyJwt($jwt);            // signature + exp check
    if ($claims === null) return null;
    // Forward minimal trusted identity to internal services
    return ['X-User-Id' => $claims['sub'], 'X-Scopes' => implode(',', $claims['scopes'])];
}
function verifyJwt(string $j): ?array { return ['sub' => 'u-7', 'scopes' => ['orders:read']]; }
print_r(authenticate('Bearer abc.def.ghi'));

تجميع الاستجابات

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

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;

$http = new Client(['timeout' => 2.0]);
$promises = [
    'order'    => $http->getAsync('http://orders-svc/orders/42'),
    'customer' => $http->getAsync('http://customers-svc/customers/7'),
    'catalog'  => $http->getAsync('http://catalog-svc/items?order=42'),
];
$results = Utils::settle($promises)->wait(); // run in parallel
// merge the fulfilled bodies into one response for the client

الخدمات الخلفية للواجهات الأمامية

غالبًا لا تستطيع بوابة عامة واحدة خدمة تطبيق ويب وتطبيق جوّال وشركاء بالقدر نفسه — فلكل منها احتياجات مختلفة للتجميع وبُنى مختلفة للحمولة. يمنح نمط Backend-for-Frontend (BFF) كل نوع من العملاء بوابته الخفيفة الخاصة، والمصممة وفق احتياجاته، بينما تظل الخدمات المشتركة عامة.

يتجنب ذلك بوابة ضخمة تتحكم في كل شيء، ويتيح لكل فريق يعمل على عميل أن يتقدم بشكل مستقل.

مشكلة اكتشاف الخدمات

في بيئة ديناميكية، تظهر المثيلات وتختفي وتتغير عناوين IP الخاصة بها. يُعدّ تثبيت http://10.0.3.14:8080 في الشيفرة هشًا. يحتفظ اكتشاف الخدمات بسجل حيّ لـ «أي مثيلات سليمة من الخدمة X موجودة الآن»، بحيث يحلّ المستدعون اسمًا منطقيًا إلى عنوان فعلي وقت الاستدعاء.

الاكتشاف من جانب العميل مقابل الاكتشاف من جانب الخادم

نموذجان:

  • من جانب العميل — يستعلم المستدعي من سجل (Consul, etcd) ويختار مثيلًا بنفسه، ويتولى موازنة الحمل بنفسه.
  • من جانب الخادم — يتصل المستدعي بعنوان افتراضي ثابت (موازن حمل / Kubernetes Service) يتولى حل العنوان وموازنة الحمل نيابةً عنه.

في Kubernetes تحصلون عادةً على اكتشاف من جانب الخادم مجانًا: استدعوا http://customers-svc وسيتولى DNS الخاص بالعنقود وService بقية العمل. وخارج k8s، تُعد سجلات Consul من الأنماط الشائعة.

الاستعلام من سجل

مع اكتشاف من جانب العميل، يطلب مستدعي PHP من السجل مثيلات سليمة ويختار أحدها. لا يعيد السجل إلا المثيلات التي تجتاز فحوصات الصحة، ولذلك تُستبعد العقد المتوقفة تلقائيًا.

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;

function discover(Client $http, string $service): string {
    // Consul: only passing-health instances
    $res = $http->get("http://consul:8500/v1/health/service/$service?passing=true");
    $nodes = json_decode((string) $res->getBody(), true);
    if (!$nodes) throw new \RuntimeException("No healthy $service");
    $pick = $nodes[array_rand($nodes)]['Service']; // simple LB
    return "http://{$pick['Address']}:{$pick['Port']}";
}

فحوصات الصحة والتسجيل

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

اجعلوا فحص الصحة ذا معنى: إن إعادة 200 أثناء توقف قاعدة البيانات أسوأ من انعدام الفائدة — فهي توجّه حركة المرور إلى عقدة معطلة.

<?php
// GET /health
function health(PDO $db, Redis $cache): array {
    $checks = [
        'db'    => safe(fn() => $db->query('SELECT 1') !== false),
        'cache' => safe(fn() => $cache->ping() === '+PONG'),
    ];
    $ok = !in_array(false, $checks, true);
    http_response_code($ok ? 200 : 503);
    return ['status' => $ok ? 'pass' : 'fail', 'checks' => $checks];
}
function safe(callable $c): bool { try { return (bool) $c(); } catch (\Throwable) { return false; } }

الحيوية مقابل الجاهزية

لا تكفي نقطة نهاية واحدة للصحة — ميّزوا بين سؤالين:

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

يؤدي الخلط بينهما إلى حلقات إعادة تشغيل أو توجيه الطلبات إلى مثيلات غير جاهزة بعد.

<?php
// GET /livez  - is the process itself healthy? (no external deps)
function livez(): void { http_response_code(200); echo 'alive'; }

// GET /readyz - should we receive traffic? (checks dependencies)
function readyz(PDO $db): void {
    try { $db->query('SELECT 1'); http_response_code(200); echo 'ready'; }
    catch (\Throwable) { http_response_code(503); echo 'not ready'; }
}

تحقق سريع

تقليل عدد الرحلات ذهابًا وإيابًا التي يجريها العميل.

مراجعة

توجيه الخدمات والعثور عليها:

  • تُركّز بوابة API التوجيه والمصادقة وتحديد معدل الطلبات وTLS والتجميع في مكان واحد.
  • نفّذوا التجميع بالتزامن؛ واستخدموا BFFs عندما تختلف احتياجات العملاء.
  • يحلّ اكتشاف الخدمات الأسماء المنطقية إلى مثيلات حية وسليمة.
  • الاكتشاف من جانب العميل (الاستعلام من سجل) مقابل الاكتشاف من جانب الخادم (موازن حمل ثابت / k8s Service).
  • تُبقي فحوصات الصحة ذات المعنى حركة المرور بعيدًا عن العقد المعطلة.

التالي: الحفاظ على مرونة كل هذه الاستدعاءات عند تعطل أجزاء منها حتمًا.

الأسئلة الشائعة

هل درس «بوابات API واكتشاف الخدمات» مجاني؟

نعم — نص درس «بوابات API واكتشاف الخدمات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «بوابات API واكتشاف الخدمات»؟

وجّه الخدمات واجمعها وحدّد مواقعها ديناميكيًا تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «بوابات API واكتشاف الخدمات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. من التطبيق الأحادي إلى الخدمات المصغّرة
  2. تواصل الخدمات: REST وgRPC
  3. بوابات API واكتشاف الخدمات
  4. المرونة: قواطع الدائرة وإعادة المحاولة
← العودة إلى PHP Academy