تصميم واجهات البرمجة وأنماط الخدمات المصغرة

أنماط بنية الخدمات المصغرة

5 دقيقة للقراءة

تهيمن أسئلة الخدمات المصغرة على مقابلات المهندسين الأوائل. تحتاج أن تشرح الأنماط وترسم مخططات البنية وتتحدث عن المقايضات. يغطي هذا الدرس الأنماط الأكثر سؤالًا عنها في المقابلات.

من المتراص إلى الخدمات المصغرة: نمط التين الخانق

لا تعد كتابة تطبيق متراص من الصفر أبدًا. نمط التين الخانق يتيح لك استخراج الخدمات تدريجيًا بينما يستمر التطبيق المتراص في العمل.

                    ┌─────────────┐
                    │   بوابة     │
                    │   الواجهة   │
                    │   البرمجية  │
                    └──────┬──────┘
              ┌────────────┼────────────┐
              │            │            │
              v            v            v
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │  خدمة    │ │  خدمة    │ │ المتراص  │
        │  المصادقة│ │  الدفع   │ │ (لا يزال │
        │  الجديدة │ │  الجديدة │ │ يتعامل   │
        └──────────┘ └──────────┘ │ مع       │
                                   │ الطلبات  │
                                   │ والمستخدم│
                                   │ وغيرها)  │
                                   └──────────┘

كيف يعمل:

  1. ضع بوابة واجهة برمجية أمام التطبيق المتراص
  2. حدد سياقًا محددًا لاستخراجه (ابدأ بالأقل ارتباطًا)
  3. ابنِ الخدمة الجديدة بجانب التطبيق المتراص
  4. وجّه حركة المرور إلى الخدمة الجديدة عبر البوابة
  5. أزل الكود القديم من المتراص بمجرد استقرار الخدمة الجديدة
  6. كرر للسياق المحدد التالي

حدود الخدمات: السياقات المحددة من DDD

كل خدمة مصغرة يجب أن تمتلك سياقًا محددًا واحدًا — نطاق متماسك ببياناته وقواعده ولغته الخاصة.

┌──────────────────────────────────────────────────────────┐
│                    منصة التجارة الإلكترونية               │
├──────────────┬──────────────┬──────────────┬─────────────┤
│   سياق      │   سياق      │  سياق       │  سياق       │
│   الطلبات   │   الدفع     │  المخزون    │  الشحن      │
│              │              │              │             │
│  - Order     │  - Payment   │  - Product   │  - Shipment │
│  - OrderItem │  - Refund    │  - Stock     │  - Tracking │
│  - Cart      │  - Invoice   │  - Warehouse │  - Carrier  │
│              │              │              │             │
│  قاعدة بيانات│  قاعدة بيانات│  قاعدة بيانات│  قاعدة بيانات│
│  خاصة       │  خاصة       │  خاصة       │  خاصة       │
└──────────────┴──────────────┴──────────────┴─────────────┘

المبدأ الأساسي: قاعدة بيانات لكل خدمة. كل خدمة تمتلك بياناتها. لا قواعد بيانات مشتركة. التواصل يحدث عبر الواجهات البرمجية أو الأحداث، وليس أبدًا عبر الوصول المباشر لقاعدة البيانات.

التواصل بين الخدمات

متزامن مقابل غير متزامن

الجانب متزامن (REST/gRPC) غير متزامن (طابور رسائل)
الارتباط ارتباط زمني (المتصل ينتظر) منفصل (أرسل وانسَ)
زمن الاستجابة أعلى للسلاسل (A -> B -> C) زمن استجابة محسوس أقل
التوفر فشل متتالي إذا تعطلت خدمة تابعة مرن، الرسائل تنتظر في الطابور
تصحيح الأخطاء أسهل (تتبع طلب/استجابة) أصعب (تتبع عبر الطوابير)
اتساق البيانات فوري (ضمن الطلب) اتساق نهائي
الأفضل لـ القراءات، الاستعلامات التي تحتاج استجابة فورية الكتابات، الأحداث، المهام طويلة المدة

المراسلة غير المتزامنة

┌─────────┐     ┌─────────────────┐     ┌───────────┐
│  خدمة   │────>│  وسيط الرسائل  │────>│  خدمة     │
│  الطلبات│     │  (Kafka/SQS/    │     │  الدفع    │
└─────────┘     │   RabbitMQ)     │     └───────────┘
                └────────┬────────┘
                         ├────────────>┌───────────┐
                         │             │ خدمة      │
                         │             │ المخزون   │
                         │             └───────────┘
                         └────────────>┌───────────┐
                                       │ خدمة      │
                                       │ الإشعارات │
                                       └───────────┘
الوسيط نقاط القوة الأفضل لـ
Apache Kafka إنتاجية عالية، سجل دائم، إعادة تشغيل مصادر الأحداث، البث، سجلات التدقيق
RabbitMQ توجيه مرن، طوابير أولوية، زمن استجابة منخفض طوابير المهام، أنماط RPC
AWS SQS مُدار بالكامل، تحجيم تلقائي، طوابير الرسائل الميتة البنيات بدون خادم، البيئات الأصلية لـ AWS

نمط الملحمة: المعاملات الموزعة

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

مثال تدفق طلب التجارة الإلكترونية

المسار السعيد:
  خدمة الطلبات    خدمة الدفع       خدمة المخزون      خدمة الشحن
       │                    │                    │                    │
  1. إنشاء طلب ─────────────>                    │                    │
       │              2. خصم البطاقة              │                    │
       │              ────────────────>           │                    │
       │                    │           3. حجز المخزون               │
       │                    │           ─────────────────>            │
       │                    │                    │          4. إنشاء الشحنة
       │                    │                    │          ────────────────>
       │                    │                    │                    │
       ◄────────────────────────────── نجاح ─────────────────────────┘

الفشل في الخطوة 3 (نفاد المخزون):
  خدمة الطلبات    خدمة الدفع       خدمة المخزون
       │                    │                    │
  1. إنشاء طلب ─────────────>                    │
       │              2. خصم البطاقة              │
       │              ────────────────>           │
       │                    │           3. حجز المخزون ──> فشل!
       │                    │                    │
       │              4. تعويض:                   │
       │                 استرداد البطاقة          │
       │              <──────────────             │
  5. تعويض:                 │                    │
     إلغاء الطلب           │                    │
       │                    │                    │

التنسيق مقابل التصميم الرقصي

التنسيق: منسق مركزي (منسق الملحمة) يخبر كل خدمة ماذا تفعل.

                    ┌──────────────────┐
                    │  منسق           │
                    │  الملحمة        │
                    └───────┬──────────┘
              ┌─────────────┼─────────────┐
              │             │             │
              v             v             v
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │  خدمة   │ │  خدمة   │ │  خدمة   │
        │  الدفع  │ │ المخزون │ │  الشحن  │
        └──────────┘ └──────────┘ └──────────┘

التصميم الرقصي: كل خدمة تتفاعل مع الأحداث وتنشر أحداثها الخاصة. لا منسق مركزي.

  طلب أُنشئ ──> خدمة الدفع ──> الدفع اكتمل ──> خدمة المخزون
                                                 المخزون حُجز
                                                        v
                                                  خدمة الشحن
الجانب التنسيق التصميم الرقصي
التنسيق منسق مركزي موزع (كل خدمة تستمع)
الارتباط الخدمات مرتبطة بالمنسق الخدمات مرتبطة بالأحداث
الرؤية سهل رؤية التدفق الكامل في مكان واحد التدفق موزع عبر الخدمات
التعقيد المنسق قد يصبح معقدًا أصعب تتبع الملحمة الكاملة
معالجة الفشل المنسق يدير التعويضات كل خدمة تتعامل مع فشلها
الأفضل لـ تدفقات العمل المعقدة متعددة الخطوات التدفقات البسيطة، الاستقلالية العالية
المخاطر نقطة فشل واحدة تبعيات دائرية، عواصف أحداث

إجابة المقابلة: "لتدفق دفع من أكثر من 4 خطوات مع ترتيب صارم، سأستخدم التنسيق — الرؤية المركزية تسهل التعامل مع المعاملات التعويضية وتصحيح الأخطاء. للتدفقات البسيطة القائمة على الأحداث مثل إرسال الإشعارات بعد الطلب، التصميم الرقصي يعمل جيدًا لأنه يبقي الخدمات مستقلة."

نمط قاطع الدائرة

عندما تفشل خدمة تابعة، يتوقف قاطع الدائرة عن استدعائها لمنع الفشل المتتالي. له ثلاث حالات:

                          ┌──────────┐
                          │  مغلق   │  (التشغيل العادي)
                          │          │
                          │ الطلبات  │
                          │ تمر      │
                          │          │
                          └────┬─────┘
                    عتبة الفشل وصلت
                               v
                          ┌──────────┐
                          │  مفتوح  │  (جميع الطلبات تفشل فورًا)
                          │          │
                          │ يرجع    │
                          │ بديل    │
                          │ أو خطأ  │
                          └────┬─────┘
                      المهلة انتهت
                               v
                          ┌──────────┐
                          │نصف مفتوح│  (اختبار بطلبات محدودة)
                          │          │
                          │ يسمح    │
                          │ بطلبات  │
                          │ اختبار  │
                          └────┬─────┘
                    ┌──────────┴──────────┐
                    │                     │
              الاختبارات نجحت       الاختبارات فشلت
                    │                     │
                    v                     v
               ┌──────────┐         ┌──────────┐
               │  مغلق   │         │  مفتوح  │
               └──────────┘         └──────────┘
import time
from enum import Enum

class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

class CircuitBreaker:
    def __init__(self, failure_threshold: int = 5, timeout: float = 30.0, half_open_max: int = 3):
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.half_open_max = half_open_max

        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.last_failure_time = 0
        self.half_open_calls = 0

    def call(self, func, *args, **kwargs):
        if self.state == CircuitState.OPEN:
            if time.time() - self.last_failure_time > self.timeout:
                self.state = CircuitState.HALF_OPEN
                self.half_open_calls = 0
            else:
                raise CircuitOpenError("الدائرة مفتوحة — الطلب محجوب")

        if self.state == CircuitState.HALF_OPEN:
            if self.half_open_calls >= self.half_open_max:
                raise CircuitOpenError("حد النصف مفتوح وصل")
            self.half_open_calls += 1

        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise e

    def _on_success(self):
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.CLOSED
        self.failure_count = 0

    def _on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.failure_count >= self.failure_threshold:
            self.state = CircuitState.OPEN

بوابة الواجهة البرمجية

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

                          ┌──────────────────────┐
    تطبيق الجوال ────────>│                      │──> خدمة المستخدمين
    تطبيق الويب ─────────>│   بوابة الواجهة     │──> خدمة الطلبات
    طرف ثالث ────────────>│   البرمجية          │──> خدمة الدفع
                          │                      │──> خدمة المخزون
                          │  - التوجيه           │
                          │  - المصادقة          │
                          │  - تحديد المعدل      │
                          │  - تجميع الطلبات     │
                          │  - إنهاء SSL         │
                          │  - موازنة الحمل      │
                          └──────────────────────┘

الخيارات الشائعة: Kong (قائم على الإضافات)، Envoy (وكيل عالي الأداء)، AWS API Gateway (بدون خادم)، NGINX (خفيف الوزن).

شبكة الخدمات

لعمليات نشر الخدمات المصغرة المعقدة، تضيف شبكة الخدمات المراقبة والأمان وإدارة حركة المرور بدون تغيير كود التطبيق. تستخدم نمط الوكيل الجانبي.

  ┌─────────────────────┐     ┌─────────────────────┐
  │  Pod A              │     │  Pod B              │
  │  ┌───────────────┐  │     │  ┌───────────────┐  │
  │  │  خدمة        │  │     │  │ خدمة          │  │
  │  │  الطلبات     │  │     │  │ الدفع         │  │
  │  └───────┬───────┘  │     │  └───────┬───────┘  │
  │          │          │     │          │          │
  │  ┌───────▼───────┐  │     │  ┌───────▼───────┐  │
  │  │  وكيل Envoy  │◄─┼─mTLS┼─►│  وكيل Envoy  │  │
  │  │  (جانبي)     │  │     │  │  (جانبي)     │  │
  │  └───────────────┘  │     │  └───────────────┘  │
  └─────────────────────┘     └─────────────────────┘
            │                           │
            └───────────┬───────────────┘
                ┌───────▼────────┐
                │  مستوى التحكم │
                │  (Istio/Linkerd)│
                │  - شهادات mTLS │
                │  - قواعد المرور│
                │  - المراقبة   │
                └────────────────┘

ما توفره شبكة الخدمات:

  • mTLS: تشفير تلقائي بين جميع الخدمات — شبكات عدم الثقة
  • إدارة المرور: نشر الكناري، اختبار A/B، حقن الأخطاء
  • المراقبة: تتبع موزع، مقاييس، سجلات وصول — بدون تغيير الكود
  • إعادة المحاولة والمهلات: سياسات إعادة محاولة قابلة للتكوين على مستوى الوكيل

CQRS (فصل مسؤولية الأمر والاستعلام)

افصل نماذج القراءة والكتابة في تطبيقك. جانب الكتابة يُحسّن للاتساق، بينما جانب القراءة يُحسّن لأداء الاستعلام.

                    ┌──────────────────────────────────────┐
                    │           طبقة الواجهة البرمجية      │
                    └────────────┬─────────────────────────┘
                    ┌────────────┴────────────┐
                    │                         │
            الأوامر (كتابة)           الاستعلامات (قراءة)
                    │                         │
                    v                         v
            ┌──────────────┐         ┌──────────────┐
            │ نموذج الكتابة│         │ نموذج القراءة│
            │ (مُطبّع،    │  مزامنة │ (غير مطبّع، │
            │  متسق)      │ ──────> │ قراءات سريعة)│
            │              │ أحداث  │              │
            │  PostgreSQL  │         │ Elasticsearch│
            │              │         │  أو Redis    │
            └──────────────┘         └──────────────┘

متى تستخدم CQRS:

  • أعباء عمل كثيفة القراءة (نسبة قراءة/كتابة 100:1)
  • استعلامات معقدة تبطئ قاعدة بيانات الكتابة
  • احتياجات تحجيم مختلفة للقراءة مقابل الكتابة
  • الحاجة لتمثيلات بيانات مختلفة (مثل فهرس البحث)

متى لا تستخدم CQRS:

  • تطبيقات CRUD البسيطة
  • حركة مرور منخفضة حيث يكفي نموذج واحد
  • فرق غير معتادة على الاتساق النهائي

مصادر الأحداث

بدلاً من تخزين الحالة الحالية، خزّن تسلسل الأحداث التي أدت إلى تلك الحالة. يمكنك إعادة بناء أي حالة بإعادة تشغيل الأحداث.

التقليدي (قائم على الحالة):
  الحساب: { id: "acc-1", balance: 150 }

مصادر الأحداث (قائم على الأحداث):
  الحدث 1: AccountCreated  { id: "acc-1", owner: "Alice" }
  الحدث 2: MoneyDeposited  { amount: 200 }
  الحدث 3: MoneyWithdrawn  { amount: 50 }
  ─────────────────────────────────────────
  الحالة الحالية: الرصيد = 0 + 200 - 50 = 150
// مصادر الأحداث مع مخزن أحداث
interface DomainEvent {
  eventId: string;
  aggregateId: string;
  eventType: string;
  payload: Record<string, unknown>;
  timestamp: string;
  version: number;
}

// إعادة بناء حالة الحساب من الأحداث
function rebuildAccount(events: DomainEvent[]): Account {
  return events.reduce((account, event) => {
    switch (event.eventType) {
      case 'AccountCreated':
        return { id: event.aggregateId, balance: 0, owner: event.payload.owner as string };
      case 'MoneyDeposited':
        return { ...account, balance: account.balance + (event.payload.amount as number) };
      case 'MoneyWithdrawn':
        return { ...account, balance: account.balance - (event.payload.amount as number) };
      default:
        return account;
    }
  }, {} as Account);
}

فوائد مصادر الأحداث:

  • مسار تدقيق كامل — كل تغيير مسجل
  • السفر عبر الزمن — إعادة بناء الحالة في أي نقطة زمنية
  • إعادة تشغيل الأحداث — إصلاح الأخطاء بإعادة تشغيل الأحداث عبر منطق مصحح
  • تناسب طبيعي مع CQRS — الأحداث تحدث نموذج القراءة

التحديات:

  • الاتساق النهائي بين مخزن الأحداث ونموذج القراءة
  • تطور مخطط الأحداث (إصدار الأحداث)
  • نمو التخزين — يحتاج لقطات للتجميعات ذات الأحداث الكثيرة

نصيحة للمقابلة: CQRS ومصادر الأحداث يُذكران معًا كثيرًا لكنهما نمطان مستقلان. يمكنك استخدام CQRS بدون مصادر الأحداث (مزامنة نماذج القراءة من قاعدة بيانات الكتابة) ومصادر الأحداث بدون CQRS (نموذج واحد يُعاد بناؤه من الأحداث).

هذا يكمل وحدة تصميم واجهات البرمجة والخدمات المصغرة. اختبر معرفتك مع اختبار الوحدة، ثم طبّق هذه الأنماط في مختبر تصميم واجهة برمجية للدفع. :::

اختبار

اختبار الوحدة 3: تصميم واجهات البرمجة وأنماط الخدمات المصغرة

خذ الاختبار