أنماط المعمارية

نمط Strangler Fig: هجرة المعمارية الأحادية

18 دقيقة الدرس 8 من 10

نمط Strangler Fig: هجرة المعمارية الأحادية

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

سُمِّي نمط Strangler Fig على يد مارتن فاولر نسبةً إلى شجرة التين الخانقة الاستوائية التي تنمو حول شجرة مضيفة وتحل محلها في نهاية المطاف. وهو الأسلوب الصناعي المعياري للهجرة التدريجية للمعمارية الأحادية الحية إلى خدمات مصغرة أو أي معمارية أحدث، دون المخاطرة بإعادة الكتابة الكاملة دفعة واحدة.

نمط فشل "إعادة الكتابة الكاملة": في عام 2000، حاولت شركة Netscape إعادة كتابة متصفحها من الصفر. استغرق المشروع ثلاث سنوات، وكلّف مئات الملايين من الدولارات، ويُرجَّح أنه كان من الأسباب الرئيسية في تدمير تفوق الشركة في السوق. وصفها جويل سبولسكي بأنها "أسوأ خطأ استراتيجي يمكن أن ترتكبه شركة برمجيات على الإطلاق". نمط Strangler Fig وُجد تحديدًا لتفادي هذا المصير.

الفكرة الأساسية

بدلًا من إعادة كتابة المعمارية الأحادية دفعةً واحدة، تُدخل طبقة بروكسي أو واجهة وسيطة (Facade) أمامها. تُوجَّه طلبات الميزات الجديدة والقدرات المُهاجَرة تدريجيًّا إلى الخدمات الجديدة، بينما يستمر المونوليث في التعامل مع كل شيء آخر. مع الوقت، قطعةً قطعةً، تخنق المونوليث — حتى يمكن إيقافه تمامًا أو يتعامل فقط مع شريحة ضئيلة من الحركة.

ثلاث مراحل تُهيكل كل هجرة بنمط Strangler Fig:

  1. التحويل (Transform) — بناء الخدمة الجديدة بشكل متوازٍ دون لمس المونوليث.
  2. التعايش (Co-exist) — نشر البروكسي أمام كليهما، وتوجيه جزء من الحركة إلى الخدمة الجديدة.
  3. الإزالة (Eliminate) — حين تتولى الخدمة الجديدة 100% من قدرة معينة، تُزال تلك الشيفرة من المونوليث.
Strangler Fig migration phases — three-phase progression from monolith to decomposed services Phase 1: Transform Phase 2: Co-exist Phase 3: Eliminate Client Monolith Orders Users Billing Notifications Catalog New Catalog Service (built) (not yet live) Client Proxy / Facade 80% Monolith Orders Users Billing Notifications ~~Catalog~~ 20% Catalog Service Client Proxy / Facade Monolith Orders (shrinking) Cat User Notif Migration progresses over months or years →
المراحل الثلاث لنمط Strangler Fig: بناء الخدمة الجديدة، والتعايش خلف بروكسي، ثم الإزالة من المونوليث.

البروكسي: مستوى التحكم في الهجرة

البروكسي (يُسمى أحيانًا الـFacade أو Strangler Facade) هو محور النمط بأكمله. يقف بين العملاء والخلفية، يفحص كل طلب ويقرر توجيهه إلى المونوليث القديم أو الخدمة الجديدة. من الناحية العملية يُنفَّذ عادةً كـ:

  • بوابة API (مثل AWS API Gateway أو Kong أو Nginx) مع قواعد توجيه قائمة على المسار.
  • عكسي بروكسي يندرج ضمن بنيتك التحتية القائمة ويمكن تحديثه دون تغييرات في الشيفرة.
  • Feature flags مُدمجة في البروكسي لتفعيل عمليات الطرح التدريجي (Canary): 5% ثم 25% ثم 100%.

قاعدة حاسمة: يجب ألا يحمل البروكسي أي منطق أعمال. مهمته فقط التوجيه. منطق الأعمال في البروكسي يتحول إلى مونوليث جديد من نوعه.

هجرة البيانات: الجزء الأصعب

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

  1. النسخ المزدوج (Duplicate) — أثناء التعايش، اكتب كل التغييرات في كلٍّ من قاعدة بيانات المونوليث وقاعدة بيانات الخدمة الجديدة (كتابة مزدوجة أو مزامنة بالأحداث).
  2. التحقق (Verify) — شغِّل وظيفة توفيق تُثبت اتساق كلا المخزنين لفترة من الزمن (أيام أو أسابيع).
  3. قطع التحويل (Cut over) — أوقف كتابة المونوليث في ذلك الجدول؛ الخدمة الجديدة تمتلك البيانات حصريًّا.
  4. الإزالة (Remove) — احذف الجدول من مخطط المونوليث.
الكتابة المزدوجة خطيرة إن لم تُعالَج بعناية. إذا نجحت الكتابة في قاعدة بيانات المونوليث وفشلت في قاعدة بيانات الخدمة الجديدة، أصبح لديك انقسام في الاتساق. استخدم نمط Outbox أو Event Sourcing لجعل الكتابة المزدوجة موثوقة: يكتب المونوليث حدثًا في جدول Outbox ضمن نفس المعاملة، ثم تقوم عملية مرحلية بنشره إلى وسيط الرسائل الذي تستهلكه الخدمة الجديدة.
Safe data migration using the outbox pattern during Strangler Fig migration Proxy Facade Monolith writes order + outbox event (1 transaction) Monolith DB (+ outbox table) Outbox Relay Message Broker (Kafka) New Service consumes events New Service DB ① Atomic write to monolith DB + outbox ② Relay publishes → broker → new service syncs DB
الكتابة المزدوجة الآمنة عبر نمط Outbox: يكتب المونوليث في قاعدة بياناته وجدول Outbox ذريًّا، ثم تنشر عملية الريلاي الأحداث إلى وسيط الرسائل لتستهلكها الخدمة الجديدة.

مثال واقعي: Shopify

عملت Shopify على معمارية Rails أحادية واحدة لأكثر من عقد. بحلول عام 2016 كان أكثر من 600 مهندس يعملون في نفس القاعدة البرمجية وأصبحت عمليات النشر أكبر عائق أمام الفريق. بدلًا من إعادة الكتابة الكاملة، نفّذت Shopify هجرة بنمط Strangler Fig على مدى عدة سنوات:

  • أولًا، عملت على تنظيم المونوليث داخليًّا — بفرض حدود صارمة للوحدات وإزالة استدعاءات قاعدة البيانات عبر الوحدات. كانت هذه مرحلة "التحويل".
  • استُخرجت القدرات الأعلى حركةً والأكثر عزلًا أولًا: محرك عرض واجهة المتجر، ومعالجة المدفوعات، ومنصة API الخاصة بها.
  • طبقة بروكسي داخلية (تطورت لاحقًا إلى شبكة خدمات مكتوبة بـ Golang) وجّهت مساحات أسماء URL محددة إلى الخدمات الجديدة بينما تعامل المونوليث مع كل شيء آخر.
  • استغرق كل استخراج فريقًا مخصصًا 3-6 أشهر تشمل هجرة البيانات وهجرة الحركة وفترة التوفيق.

بحلول عام 2020 استخرجت Shopify عشرات الخدمات لكن المونوليث — الآن أصغر بكثير ومعياري — كان لا يزال يتعامل مع جزء كبير من حركة المرور. الهجرة لم "تنته" وربما لن تنتهي أبدًا. هذا مقصود: النمط يتعلق بالتقدم المستمر المدار بحذر، لا بالوصول إلى وجهة محددة.

اختيار ما يُستخرج أولًا

ليست كل الوحدات مرشحةً متساوية للاستخراج المبكر. استخدم إطار تحديد الأولويات التالي:

  • قيمة عالية، ارتباط منخفض. الوحدة التي لها عدد قليل من التبعيات الواردة من الوحدات الأخرى لكنها تسبب ألمًا كبيرًا (اختناقات النشر، الأعطال المتكررة) هي المرشح المثالي للاستخراج الأول. أنظمة الإشعارات ووحدات تخزين الملفات غالبًا ما تنطبق عليها هذه الخصائص.
  • توافر نقاط التماس (Seams). مفهوم "الـ Seam" عند فريد بروكس — المكان الذي يمكنك فيه إدخال سلوك جديد دون تغيير الشيفرة القائمة — حاسم. استخرج على طول حدود الوحدات القائمة لا بقطع عشوائية.
  • وضوح ملكية البيانات. ابدأ بالوحدات التي تمتلك بياناتها بوضوح ولا تشارك في معاملات متعددة الجداول مع وحدات أخرى. الفوترة والمدفوعات، رغم قيمتها العالية، غالبًا ما تحمل تبعيات بيانات معقدة ومتشابكة ولا ينبغي أن تكون الأولى.
  • توافق ملكية الفريق. استخرج وحدة حين يستطيع فريق واحد تحمّل ملكيتها الكاملة. وحدة يشاركها ثلاثة فرق مختلفة هي كابوس تنسيق في الاستخراج.
ارسم مخطط "نجمة الموت" أولًا. قبل الهجرة، ارسم كل وحدة كعقدة وكل استدعاء بين الوحدات كحافة موجهة. النتيجة عادةً تشابك كثيف (يسميه المهندسون مخطط "نجمة الموت"). الوحدات ذات الحواف الواردة القليلة هي أأمن عمليات الاستخراج المبكر؛ الوحدات الأساسية شديدة الترابط ينبغي أن تكون الأخيرة.

المخاطر والتخفيفات

  • البروكسي يصبح نقطة اختناق أو نقطة فشل وحيدة. انشر البروكسي في مجموعة مع توسع تلقائي؛ أضف فحوصات الصحة وقواطع الدوائر على كلا المسارين.
  • التعايش المفتوح غير المحدود. أحيانًا يفقد الفرق زخمه بعد أول استخراج ولا يُكمل الهجرة. حدد تاريخ إيقاف مكتوبًا لكل قدرة مُهاجَرة واحتسب الفريق على تحقيقه.
  • زيادة الكمون أثناء التعايش. استدعاء متزامن كان يجري في الذاكرة ضمن المونوليث يسافر الآن عبر الشبكة. قِس المسارات الأكثر استخدامًا أولًا؛ قيّم ما إذا كانت الاتصالات غير المتزامنة مقبولة للوحدة المُستخرجة.
  • تعقيد اختبار الأنظمة الموزعة. اختبارات التكامل التي تضرب البروكسي يجب الآن أن تتعامل مع خلفيتين مختلفتين. استثمر في اختبارات العقود (مثل Pact) للتحقق من أن API الخدمة الجديدة يطابق ما يتوقعه البروكسي والمتصلون الآخرون.

الخلاصة

نمط Strangler Fig ليس فاخرًا — إنه عمل بطيء ومنهجي يمتد لأشهر أو سنوات. لكنه الأسلوب الوحيد الآمن للإنتاج لهجرة مونوليث حي. المبادئ الأساسية هي: حافظ دائمًا على نظام يعمل، استخدم البروكسي للتحكم في توزيع الحركة، انقل ملكية البيانات قبل إيقاف الشيفرة، استخرج على طول نقاط التماس ذات الملكية الواضحة، وحافظ على الزخم بمعالم إيقاف مكتوبة. كل شركة نجحت في تفكيك مونوليث كبير استخدمت نوعًا من هذا النمط.