من التطبيق الأحادي إلى الخدمات المصغّرة
حدّد ما ينبغي تقسيمه وكيفية رسم حدود الخدمات
من التطبيق الأحادي إلى الخدمات المصغّرة درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.
من التطبيق الأحادي إلى الخدمات المصغّرة
الخدمات المصغّرة رهان تنظيمي وتشغيلي، وليست خيارًا افتراضيًا. فتقسيم تطبيق PHP الأحادي يستبدل بساطة التنفيذ داخل العملية باستدعاءات الشبكة والبيانات الموزّعة وتعقيد النشر. وإذا أُجري التقسيم لأسباب خاطئة، فسيجعل كل شيء أبطأ.
يتناول هذا الدرس الجزء الأصعب: رسم حدود الخدمات. إذا أصبتم في تحديد نقاط الفصل، يصبح الباقي أعمال توصيل؛ أما إذا أخطأتم فيها، فستبنون تطبيقًا أحاديًا موزّعًا — أي كل الكلفة من دون أي فائدة.
لماذا نقسّم (ولماذا لا نقسّم)
أسباب وجيهة للتقسيم:
- قابلية النشر المستقلة وملكية الفرق المستقلة.
- التوسّع المستقل للأنظمة الفرعية التي تتعرض لضغط كبير.
- عزل التقنية وبيئة التشغيل واحتواء الأعطال.
أما الأسباب السيئة فتشمل: «الشفرة فوضوية» (أعيدوا هيكلة التطبيق الأحادي أولًا)، أو ملاحقة صيحة رائجة. فإذا كان يجب دائمًا نشر خدمتين معًا أو تشاركان جدولًا في قاعدة البيانات، فهما خدمة واحدة تؤدي دورين.
السياقات المحدودة
يقدّم التصميم الموجّه بالمجال أفضل أداة لتحديد الحدود: السياق المحدود. داخل السياق الواحد، تحمل المصطلحات معنى دقيقًا واحدًا. فكلمة «العميل» في الفوترة (الجهة المستهدفة بالفاتورة) تختلف عن «العميل» في الدعم (كاتب التذكرة).
كل سياق محدود مرشح قوي ليكون خدمة. وتتبع الحدود لغة الأعمال وطريقة تواصل المؤسسة فعليًا — وهذا تطبيق لقانون Conway.
تماسك عالٍ، واقتران منخفض
تحافظ حدود الخدمة الجيدة على العناصر التي تتغير معًا داخلها، وتدفع بالعناصر التي تتغير بشكل مستقل إلى خارجها. قيسوا التقسيم المقترح وفقًا لما يلي:
- كم ميزة تتطلب لمس خدمتين في الوقت نفسه؟ (ينبغي أن يكون العدد قليلًا.)
- ما مدى كثرة الاستدعاءات بين الخدمتين؟ (ينبغي أن تكون الاستدعاءات كبيرة ومحدودة العدد.)
إذا كان تنفيذ ميزة واحدة يتجاوز حدًا معينًا باستمرار، فهذا يعني أن الحد في المكان الخطأ.
<?php
// Chatty boundary smell: N network calls to render one view
foreach ($order->lineItems as $item) {
$product = $catalogApi->get($item->productId); // one call PER item!
$names[] = $product['name'];
}
// Coarse-grained: one batch call across the boundary
$ids = array_map(fn($i) => $i->productId, $order->lineItems);
$names = $catalogApi->getMany($ids); // single round-trip
echo count($names) . " products in one call\n";قاعدة بيانات لكل خدمة
القاعدة التي لا تقبل التفاوض: كل خدمة تملك بياناتها، ولا تلمس أي خدمة أخرى جداولها. فقواعد البيانات المشتركة تعيد إنشاء الاقتران الذي حاولتم التخلص منه بالتقسيم — إذ يؤدي تغيير المخطط إلى تعطيل خدمات غير مرتبطة.
يعني هذا أن الاستعلامات العابرة للخدمات، التي كانت عبارة عن SQL JOIN في التطبيق الأحادي، تصبح استدعاءات API أو نماذج قراءة منسوخة. هذه هي الكلفة، وهذا هو الهدف.
<?php
// In the monolith: one JOIN across domains
$sql = 'SELECT o.id, c.email
FROM orders o JOIN customers c ON c.id = o.customer_id';
// After split: Orders service holds only the foreign id;
// it asks the Customers service for the rest (or keeps a local read model).
$order = $orderRepo->find($id); // local
$customer = $customersApi->get($order->customerId); // network call
echo $order->id . ' / ' . $customer->email . "\n";نمط التين الخانق
لا تعيدوا كتابة التطبيق الأحادي دفعة واحدة. يستخرج نمط التين الخانق المكونات تدريجيًا: إذ يوجّه جزءًا من حركة المرور إلى خدمة جديدة عبر واجهة وسيطة/وكيل، ثم ينمّيها، ولا يتخلص من مسار الشفرة القديم إلا عندما يغطيه المسار الجديد بالكامل.
يوجد وكيل عكسي (أو بوابة API) في المقدمة، ويقرر لكل مسار ما إذا كان سيتصل بالتطبيق الأحادي القديم أو بالخدمة الجديدة. وتجري عملية الترحيل قدرةً تلو الأخرى، مع بقاء كل خطوة قابلة للنشر.
<?php
// Facade routing: peel off one capability at a time
function route(string $path): string {
$migrated = ['/invoices', '/invoices/pdf']; // moved to billing-svc
foreach ($migrated as $prefix) {
if (str_starts_with($path, $prefix)) {
return 'http://billing-svc' . $path;
}
}
return 'http://legacy-monolith' . $path; // everything else, for now
}
echo route('/invoices/pdf'), "\n";
echo route('/users/42'), "\n";استخراج وحدة
ترتيب عملي للاستخراج:
- اعثروا على وحدة ذات تبعيات واردة قليلة وملكية واضحة للبيانات.
- غلّفوا استدعاءاتها داخل العملية خلف واجهة في التطبيق الأحادي أولًا.
- انقلوا البيانات التي تملكها إلى مخطط/قاعدة بيانات خاصة بها.
- استبدلوا تنفيذ الواجهة بعميل شبكي.
- حوّلوا حركة المرور عبر الواجهة الوسيطة؛ ثم احذفوا الشفرة القديمة.
يؤدي تغليف الواجهة داخل التطبيق الأحادي أولًا إلى تقليل مخاطر خطوة الانتقال إلى الشبكة.
<?php
// Step 2: hide the implementation behind a port the monolith calls
interface InvoiceService {
public function generate(string $orderId): string; // returns invoice id
}
// Today: local class. Tomorrow: HTTP client to billing-svc.
// The monolith's calling code never changes.
final class LocalInvoiceService implements InvoiceService {
public function generate(string $orderId): string { return 'inv-1'; }
}اتساق البيانات الموزّعة
بعد تقسيم البيانات، تفقدون معاملات ACID العابرة للخدمات. تقبّلوا الاتساق النهائي: تنشر الخدمات أحداثًا حول بياناتها الخاصة، وتبني الخدمات الأخرى نماذج قراءة محلية انطلاقًا من تلك الأحداث.
لا تستعلم خدمة الطلبات من العملاء عند كل طلب؛ بل تحتفظ بإسقاط صغير (لا يتضمن إلا الحقول التي تحتاج إليها)، ويُحدَّث بواسطة أحداث CustomerUpdated. ويزيل ذلك تبعية وقت التشغيل وقفزة زمنية إضافية.
<?php
// Orders service keeps a tiny local projection of customer data
function onCustomerUpdated(PDO $db, array $evt): void {
$db->prepare(
'INSERT INTO customer_read_model (id, email)
VALUES (:id, :email)
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email'
)->execute(['id' => $evt['id'], 'email' => $evt['email']]);
}الواقع التشغيلي
تنقل الخدمات المصغّرة التعقيد من الشفرة إلى العمليات. وقبل التقسيم، تحتاجون إلى ما يلي:
- تسجيل مركزي وتتبّع موزّع (معرّفات ارتباط عبر القفزات).
- CI/CD لكل خدمة وعمليات نشر مستقلة ذات إصدارات.
- فحوصات سلامة، ومهلات زمنية، وإعادات محاولة، وقواطع دوائر لكل استدعاء.
- اختبارات عقود حتى لا يتمكن المزوّد من تعطيل المستهلك بصمت.
إذا لم يتمكن فريقكم من تشغيل خدمة واحدة جيدًا، فسيكون تشغيل عشر خدمات أسوأ بعشر مرات.
تحديد الحجم المناسب للخدمات
تسمية «مصغّرة» مضللة — حدّدوا حجم الخدمات وفقًا للقدرة التجارية وملكية الفريق، لا وفقًا لعدد أسطر الشفرة. فإذا كانت الخدمات دقيقة جدًا (خدمات فائقة الصغر)، ستتفرع الميزة الواحدة إلى عاصفة من استدعاءات الشبكة؛ وإذا كانت كبيرة جدًا، فستعودون إلى تطبيق أحادي.
قاعدة إرشادية سليمة: ينبغي أن يتمكن فريق واحد من امتلاك الخدمة، وأن تكون قابلة للنشر بمفردها، وأن تنفذ حالات استخدامها الأساسية من دون سلسلة متزامنة تمر عبر عدد كبير من الخدمات النظيرة.
بديل التطبيق الأحادي المعياري
قبل الانتقال إلى نظام موزّع، ضعوا التطبيق الأحادي المعياري في الحسبان: مكوّن واحد قابل للنشر، لكنه مقسّم داخليًا إلى وحدات ذات حدود واضحة ومخططات خاصة بها، ولا تتواصل إلا عبر واجهات منشورة — من دون وصول عابر للوحدات إلى الجداول.
تحصلون على حدود واضحة وإعادة هيكلة سهلة من دون كلفة الأنظمة الموزّعة. وعندما تحتاج وحدة فعلًا إلى توسّع أو ملكية مستقلين، تكون قد صُمّمت أصلًا بحيث يمكن استخراجها عبر نمط التين الخانق. وبالنسبة إلى معظم الفرق، تُعد هذه نقطة البداية الصحيحة.
<?php
// Modules talk only through interfaces, never each other's tables.
namespace App\Billing; // owns billing_* tables
interface BillingFacade {
public function invoiceForOrder(string $orderId): string;
}
namespace App\Sales; // owns sales_* tables
final class Checkout {
public function __construct(private \App\Billing\BillingFacade $billing) {}
// Sales never SELECTs from billing_* directly - only via the facade.
}تحقّق سريع
التعرّف على تقسيم سيئ.
مراجعة
رسم حدود الخدمات بطريقة جيدة:
- قسّموا من أجل قابلية النشر والتوسّع والملكية — لا لأن الشفرة فوضوية.
- طابقوا الحدود مع السياقات المحدودة؛ واستهدفوا تماسكًا عاليًا واقترانًا منخفضًا.
- قاعدة بيانات لكل خدمة — لا جداول مشتركة؛ واستبدلوا عمليات JOIN بواجهات API أو نماذج قراءة.
- رحّلوا تدريجيًا باستخدام نمط التين الخانق.
- تقبّلوا الاتساق النهائي واستثمروا في العمليات قبل التوسّع.
التالي: كيف تتواصل هذه الخدمات فعليًا — REST وgRPC.
الأسئلة الشائعة
هل درس «من التطبيق الأحادي إلى الخدمات المصغّرة» مجاني؟
نعم — نص درس «من التطبيق الأحادي إلى الخدمات المصغّرة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماذا ستتعلم في «من التطبيق الأحادي إلى الخدمات المصغّرة»؟
حدّد ما ينبغي تقسيمه وكيفية رسم حدود الخدمات تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟
لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «من التطبيق الأحادي إلى الخدمات المصغّرة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟
نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- من التطبيق الأحادي إلى الخدمات المصغّرة
- تواصل الخدمات: REST وgRPC
- بوابات API واكتشاف الخدمات
- المرونة: قواطع الدائرة وإعادة المحاولة