احجز مكالمةتواصل معنا
العودة إلى جميع الملاحظات
١ يونيو ٢٠٢٦

بناء نقاط ويب هوك (Webhooks) مرنة للبنى التحتية الخالية من الخوادم (Serverless)

Stripe/ProviderPOST /webhooksSQS Queue15 RetriesLambda / HandlerStatus: 200 OK502

بناء نقاط ويب هوك (Webhooks) مرنة للبنى التحتية الخالية من الخوادم (Serverless)

إن بناء نقاط ويب هوك (Webhooks) مرنة للبنى التحتية الخالية من الخوادم ليس خياراً رفاهياً، بل هو متطلب أساسي وصارم. إذا كان نظامك يقبل نقاط الويب هوك الواردة من مزودي خدمات خارجيين (مثل Stripe أو GitHub أو Twilio) وكنت تعتمد على معالجة متزامنة وفورية، فإن بنيتك المعمارية معيبة بالفعل. في بيئات serverless، تجعل قيود التشغيل المتزامن (concurrency limits)، وبدايات التشغيل الباردة (cold starts)، وفشل واجهات برمجة التطبيقات (APIs) التابعة، معالجة الويب هوك بشكل متزامن وصفة مؤكدة لفقدان الأحداث وعدم اتساق حالة البيانات.

في هذا المقال، سأوضح لك بالضبط كيفية تصميم وبناء ونشر أنبوب استقبال ويب هوك غير متزامن يضمن تسليم البيانات مرة واحدة على الأقل، ويتعامل مع الارتفاعات الكبيرة في حركة المرور دون فقدان الحمولات (payloads)، ويتعافى بسلاسة من حالات انقطاع الخدمات التابعة.

المشكلة: فشل الاستقبال المتزامن

يبني معظم المهندسين نقاط نهاية الويب هوك بهذه الطريقة: يصل طلب POST إلى API Gateway، مما يؤدي إلى تشغيل وظيفة AWS Lambda، والتي تقوم بعد ذلك بالتحقق من صحة الحمولة، والاستعلام من قاعدة البيانات، واستدعاء واجهة برمجة تطبيقات خارجية، ثم إرجاع رمز الحالة 200 OK في النهاية.

يعمل هذا الأسلوب في بيئة التطوير، ولكنه ينهار سريعاً في بيئة الإنتاج الفعلية.

لماذا؟

  1. أوقات الانتظار (Timeouts): تمتلك بوابة API Gateway مهلة زمنية صارمة تبلغ 29 ثانية. إذا كانت قاعدة البيانات التابعة لك بطيئة أو واجهة برمجة التطبيقات الخارجية التي تستدعيها معلقة، فإن تنفيذ وظيفة Lambda سيتجاوز هذه المهلة. يرى موفر الخدمة الخارجي ذلك كفشل ويعيد المحاولة بشكل هجومي مكثف أو يتخلى عن الحمولة بالكامل.
  2. حدود العمليات المتزامنة (Concurrency Limits): تتوسع وظائف serverless بسرعة كبيرة، لكنها ليست محصنة ضد قيود التزامن. إذا تلقيت فجأة 10,000 طلب ويب هوك، فقد تستنفد حد التنفيذ المتزامن لحسابك، مما يتسبب في إرجاع بوابة API Gateway للرمز 429 Too Many Requests. سيعيد بعض الموفرين محاولة الإرسال عند تلقي الرمز 429، بينما لن يقوم الآخرون بذلك.
  3. الفشل الجزئي: إذا تعطلت وظيفة Lambda في منتصف عملية المعالجة - على سبيل المثال، بعد تحديث قاعدة البيانات وقبل إرسال بريد التأكيد الإلكتروني - فسينتهي بك الأمر بحالة بيانات غير متسقة ومعيبة.

لماذا يعد هذا الأمر صعباً؟

التعامل مع نقاط الويب هوك صعب بطبيعته لأنك لا تملك السيطرة على معدل الطلبات الواردة؛ فمرسل البيانات هو من يقرر متى وبأي سرعة يرسلها. وإذا لم يتمكن نظامك من امتصاص هذه الصدمة، فسوف ينهار.

وتزيد بنية serverless من تعقيد هذه المشكلة. فبينما تتوسع منصات serverless تلقائياً، فإن الموارد التي تتصل بها (مثل قواعد بيانات RDS وواجهات برمجة التطبيقات الخارجية) لا تتوسع بنفس الطريقة عادة. وينتهي بك المطاف بطبقة حوسبة عالية التوسع تضغط بشدة على طبقة تخزين هشة وغير قابلة للتوسع بنفس المعدل.

لحل هذه المشكلة، تحتاج إلى فصل المكونات (decoupling). يجب فصل عملية استقبال الويب هوك تماماً عن عملية معالجة بيانات الويب هوك.

البنية المعمارية: أنبوب الاستقبال (The Ingestion Pipeline)

الطريقة الصحيحة الوحيدة للتعامل مع نقاط الويب هوك في بيئات serverless هي الاستقبال غير المتزامن. وتبدو البنية المعمارية كما يلي:

  1. طبقة الاستقبال (Ingestion Layer): تتلقى بوابة API Gateway طلب الـ POST.
  2. طبقة الجدولة (Queueing Layer): تدفع بوابة API Gateway الحمولة مباشرة إلى طابور Amazon SQS دون إشراك وظيفة Lambda في هذه المرحلة.
  3. طبقة المعالجة (Processing Layer): يقوم تخطيط مصدر الحدث بتشغيل وظيفة Lambda لمعالجة الرسائل من طابور SQS بمعدل تزامن خاضع للتحكم.
  4. طابور الرسائل التالفة (Dead Letter Queue - DLQ): إذا فشلت معالجة رسالة ما بشكل متكرر، يتم نقلها إلى طابور DLQ للفحص اليدوي.

هذا النمط لا غنى عنه للأنظمة ذات الإنتاجية العالية. فمن خلال دمج API Gateway مباشرة مع SQS، فإنك تلغي الحاجة إلى وظيفة Lambda للاستقبال، مما يقلل التكاليف ويزيل نقطة فشل محتملة. وستقوم API Gateway بإرجاع الرمز 200 OK بشكل موثوق في غضون أجزاء من الثانية، مما يضمن لموفر الويب هوك أن عملية التسليم تمت بنجاح.

التنفيذ باستخدام AWS CDK (الإصدار v2.100.0)

دعنا نبني هذا النظام باستخدام AWS CDK بلغة TypeScript. سنقوم بتعريف طابور SQS، وطابور DLQ، ووظيفة Lambda للمعالجة، والدمج المباشر لـ API Gateway.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
import * as iam from 'aws-cdk-lib/aws-iam';

export class WebhookIngestionStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. Create the Dead Letter Queue
    const webhookDlq = new sqs.Queue(this, 'WebhookDlq', {
      retentionPeriod: cdk.Duration.days(14),
    });

    // 2. Create the Main Ingestion Queue
    const webhookQueue = new sqs.Queue(this, 'WebhookQueue', {
      visibilityTimeout: cdk.Duration.seconds(30),
      deadLetterQueue: {
        queue: webhookDlq,
        maxReceiveCount: 3,
      },
    });

    // 3. Create the Processing Lambda
    const processorLambda = new lambda.Function(this, 'ProcessorLambda', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/processor'),
      timeout: cdk.Duration.seconds(10),
    });

    // 4. Attach SQS to Lambda with controlled concurrency
    processorLambda.addEventSource(
      new lambdaEventSources.SqsEventSource(webhookQueue, {
        batchSize: 10,
        maxConcurrency: 5, // Prevent overwhelming downstream services
      })
    );

    // 5. API Gateway to SQS Direct Integration
    const api = new apigateway.RestApi(this, 'WebhookApi', {
      restApiName: 'Webhook Ingestion Service',
    });

    const integrationRole = new iam.Role(this, 'ApiGatewaySqsRole', {
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
    });
    webhookQueue.grantSendMessages(integrationRole);

    const sqsIntegration = new apigateway.AwsIntegration({
      service: 'sqs',
      path: `${cdk.Aws.ACCOUNT_ID}/${webhookQueue.queueName}`,
      integrationHttpMethod: 'POST',
      options: {
        credentialsRole: integrationRole,
        passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
        requestParameters: {
          'integration.request.header.Content-Type': `'application/x-www-form-urlencoded'`,
        },
        requestTemplates: {
          'application/json': 'Action=SendMessage&MessageBody=$util.urlEncode($input.body)',
        },
        integrationResponses: [
          {
            statusCode: '200',
            responseTemplates: {
              'application/json': '{"status":"received"}',
            },
          },
        ],
      },
    });

    api.root.addResource('webhooks').addMethod('POST', sqsIntegration, {
      methodResponses: [{ statusCode: '200' }],
    });
  }
}

منطق المعالجة (Node.js 18.x)

تقوم وظيفة Lambda الآن بسحب الرسائل من SQS. نظراً لأننا قمنا بتعيين batchSize: 10، يجب عليك التعامل مع حالات الفشل الجزئية للدفعة. إذا فشلت رسالة واحدة من أصل 10 وقمنا برمي خطأ (error)، فسيقوم SQS بإعادة محاولة إرسال جميع الرسائل العشر، وهو أمر غير فعال وخطير.

بدلاً من ذلك، قم بإرجاع معرفات الرسائل الفاشلة المحددة حتى يعيد SQS محاولة إرسال تلك الرسائل الفاشلة فقط.

// lambda/processor/index.js

exports.handler = async (event) => {
  const batchItemFailures = [];

  for (const record of event.Records) {
    try {
      const payload = JSON.parse(record.body);
      
      // Implement idempotency check here!
      // await checkIdempotency(payload.id);
      
      console.log('Processing webhook:', payload);
      
      // Simulate database write or external API call
      // await processWebhook(payload);

    } catch (error) {
      console.error(`Failed to process record ${record.messageId}`, error);
      batchItemFailures.push({ itemIdentifier: record.messageId });
    }
  }

  return { batchItemFailures };
};

أخطاء شائعة

تتفوق هذه البنية المعمارية بشكل كبير على عمليات الاستقبال المتزامنة، ولكنها تطرح تحديات جديدة يجب الانتباه إليها:

  1. تكرار العمليات (Idempotency): يضمن SQS تسليم الرسالة مرة واحدة على الأقل، مما يعني أن وظيفة Lambda الخاصة بك سوف تتلقى نفس طلب الويب هوك مرتين في نهاية المطاف. إذا قمت بمعالجة دفعة مالية من Stripe مرتين، فسوف تواجه مشاكل كبيرة مع عملائك. يجب عليك تخزين معرف الويب هوك (webhook ID) في مخزن بيانات سريع (مثل DynamoDB) والتحقق مما إذا كان قد تمت معالجته بالفعل قبل تنفيذ منطق العمل الخاص بك.
  2. ترتيب الأحداث: لا تضمن طوابير SQS القياسية ترتيب الرسائل. إذا قام مستخدم بتحديث ملفه الشخصي مرتين في ثانية واحدة، فقد تتم معالجة التحديث الثاني قبل الأول. إذا كان الترتيب أمراً بالغ الأهمية، فاستخدم طابور SQS FIFO. ومع ذلك، فإن طوابير FIFO لديها حدود إنتاجية أقل وتتطلب تكويناً دقيقاً لمعرف مجموعة الرسائل (MessageGroupId). في معظم الحالات، تكون الطوابير القياسية مقترنة بفحص طابع "آخر تحديث" (last updated timestamp) في قاعدة البيانات كافية.
  3. قيود حجم الحمولة: يبلغ الحد الأقصى لحجم حمولة SQS حوالي 256 كيلوبايت. معظم نقاط الويب هوك أصغر من هذا، ولكن إذا كنت تتوقع حمولات JSON ضخمة، فسيتعين عليك اعتراض الحمولة وتخزينها في S3، ثم تمرير مفتاح كائن S3 (object key) إلى SQS.

النتيجة

من خلال تنفيذ أنبوب الاستقبال هذا، يمكنك تحويل نظامك من نقطة نهاية متزامنة وهشة إلى آلة معالجة متينة وعالية التزامن.

تتعامل بوابة API Gateway مع الصدمة الأولى، وتستجيب لموفري الخدمة في أجزاء من الثانية. ويعمل SQS كممتص للصدمات لتخزين الحمولات في طوابير، بينما تعالج وظائف Lambda الطابور بمعدل مستقر يحمي قواعد البيانات العلائقية الخاصة بك من الإرهاق. وعند حدوث أي فشل حتمي، يلتقط طابور الرسائل التالفة (DLQ) المشكلة، مما يضمن عدم فقدان أي بيانات على الإطلاق.

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

خدمة سفن لابس

أتمتة الذكاء الاصطناعي وتكامل سير العمل

نبني أنظمة أتمتة ذات عائد استثماري قابل للقياس. شاهد خدمات الأتمتة ←
Loading...

اقرأ التالي

Stop Buying AI Tools, Start Building Systems

If your team is exhausted by software fragmentation, it is time to stop buying AI tools and start bu...

اقرأ المقال

AI Deployment in Air-Gapped Financial Networks: A Practical Architecture Guide

Learn the architecture rules for AI deployment in air-gapped networks. We cover zero-trust LLMs, loc...

اقرأ المقال
Chat with us