مقابلة تصميم النظم: مفتاحك لوظيفة مهندس برمجيات ناجح

مقابلة تصميم النظم (System Design Interview) هي مرحلة متقدمة في عملية التوظيف تمر بها غالبًا بعد المقابلة التقنية في أي شركة تتقدم إليها. يعود السبب في ذلك إلى أن هذه المقابلة تحديدًا هي التي تكشف ما إذا كانت عقليتك هي عقلية مهندس برمجيات يدرك أنه يعمل ضمن نظام متكامل ويفهم التبعات التي قد تنجم عن أي مشكلة صغيرة، أم أنك مجرد شخص يجيد كتابة الشيفرة البرمجية.

الكثير من الأشخاص قد يجتازون المقابلة التقنية بنجاح، ويكونون على دراية بـ Node.js أو Java أو أي تقنية تطلبها الشركة، ولكنهم يفشلون فشلاً ذريعاً عند دخولهم مقابلة تصميم النظم. في هذا المقال، نريد أن نفهم لماذا تحظى مقابلة تصميم النظم بهذه الأهمية، ولماذا تركز عليها الشركات لقياس مستوى خبرتك (Seniority Level).

لماذا تعتبر مقابلة تصميم النظم مهمة؟

إذا اجتزت المقابلة التقنية الأولى، التي تتضمن عادةً حل المشكلات (Problem Solving) أو كتابة الشيفرة مباشرة (Live Coding)، ثم انتقلت إلى مقابلة تصميم النظم، فهي غالبًا ما تكون العامل الحاسم في قبولك أو رفضك. كما أنها تحدد ما إذا كنت ستُعيَّن في منصب رفيع المستوى أم لا. على سبيل المثال، قد تتقدم لمنصب قائد تقني (Technical Lead)، ولكن بعد أدائك في مقابلة تصميم النظم، قد يُعرض عليك منصب مهندس برمجيات أول (Senior Software Engineer) من الدرجة الأولى.

والجدير بالذكر أن مقابلات تصميم النظم اليوم لم تعد حكرًا على المناصب العليا، بل أصبحت تُجرى أيضًا للمبتدئين (Entry Level) والخريجين الجدد (Juniors)، خاصة في الشركات متعددة الجنسيات التي تلتزم بالمعايير العالمية لهندسة البرمجيات. هذا الأمر قد لا يعرفه الكثيرون.

الشركات التي تدفع رواتب مجزية وتوفر بيئة عمل احترافية، هي تلك التي تصر على أن تمر بعملية توظيف تتضمن مقابلة تصميم النظم.

ما هي فكرة مقابلة تصميم النظم؟

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

عندما أتحدث عن مكونات النظام (System Components)، أعني أن أي نظام منطقي سيحتوي على:

وهنا تبرز الأسئلة المهمة: كيف ستتواصل هذه الخدمات؟ هل سيكون الاتصال عبر HTTP؟ أم ستختار تقنية مثل gRPC؟ أم ستعتمد على طوابير الرسائل (Queues) لتحقيق اتصال غير متزامن (Asynchronous Communication)؟

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

مثال عملي: تصميم خدمة بحث (Search Service)

لتقريب الصورة، لنتخيل أنك في مقابلة تصميم نظم، وسأطرح عليك المسألة التالية:

المطلوب: تصميم خدمة بحث (Search Service) تتميز بالمرونة والقدرة على الصمود (Resilient)، ويمكن الاعتماد عليها لتقديم نتائج استعلامات بسرعة فائقة. يجب أن يتم الرد على أي استعلام يدخل إلى الخدمة في أقل من 100 مللي ثانية (0.1 ثانية). وفي الوقت نفسه، يجب أن تقوم الخدمة بتحديث مستمر للبيانات الموجودة في قاعدة البيانات.

لنفترض أن هذا البحث سيتم على قاعدة بيانات منتجات (Products Database) في نظام تجارة إلكترونية (E-commerce System).

لنتخيل أن لديك الواجهة الخلفية لنظام التجارة الإلكترونية، وهي تتصل بقاعدة بيانات MongoDB تحتوي على بيانات المنتجات.

هيكل البيانات الأصلي للمنتج

لنفترض أن هذا هو هيكل كائن المنتج (Product) المخزن في MongoDB:

{
  "_id": "ObjectId('...')",
  "title_en": "iPhone 14 Pro",
  "title_ar": "آيفون ١٤ برو",
  "description_en": "The latest iPhone with Pro camera system.",
  "description_ar": "أحدث جهاز آيفون مع نظام كاميرا احترافي.",
  "category_id": 101,
  "brand": "Apple",
  "price": 1200,
  "currency": "USD",
  "rating": 4.8,
  "stock": 150,
  "gallery": [
    "https://example.com/iphone14-front.jpg",
    "https://example.com/iphone14-back.jpg"
  ]
}

هنا تظهر عدة تحديات.

النهج الخاطئ: إضافة البحث إلى الخدمة الحالية

أول فكرة قد تخطر على بالك هي أن تقول: “ببساطة، سأقوم بإنشاء وحدة (Module) جديدة باسم search داخل خدمة الواجهة الخلفية الحالية. سأقوم بإنشاء مسار (Route) جديد للبحث، ثم سأقوم بتنفيذ استعلام مباشر على قاعدة البيانات الحالية للبحث بالعنوان والوصف والفئة والعلامة التجارية، ثم أعيد النتائج.”

تهانينا، لقد تم رفضك في مقابلة تصميم النظم.

السبب هو أن إجابتك لا يجب أن تكون بوضع خدمة البحث كجزء من النظام الحالي. لقد ارتكبت عدة أخطاء فادحة بهذا القرار.

لماذا يعتبر هذا التصميم خاطئاً؟

  1. نقطة فشل أحادية (Single Point of Failure): أنت تقول الآن إنه إذا توقفت خدمة الواجهة الخلفية، ستتوقف معها خدمة البحث.
  2. زيادة الحمل على قاعدة البيانات: قاعدة البيانات الأساسية مسؤولة بالفعل عن منطق العمل الرئيسي (Business Logic)، مثل عرض كتالوج المنتجات وإدارة الطلبات. بإضافة استعلامات البحث المعقدة، فإنك تضع ضغطًا إضافيًا عليها، مما سيؤدي إلى زمن استجابة (Latency) مزعج وبطء في الأداء.
  3. صعوبة التخصيص: البحث يتطلب منطقًا خاصًا، مثل ترتيب النتائج بناءً على أوزان معينة (Search Weights) أو عرض النتائج الممولة (Sponsored) أولاً. إذا حاولت إضافة هذه البيانات إلى هيكل المنتج الأصلي، فإنك تضيف حملاً زائدًا على خدمة الكتالوج ببيانات لا تحتاجها.
  4. مشاكل الصيانة والتطوير: أي تحديث أو صيانة لقاعدة البيانات الرئيسية سيؤثر مباشرة على خدمة البحث. كما أن أي تغيير في خدمة الكتالوج قد يؤثر سلبًا على فريق البحث، والعكس صحيح. لقد قمت بإنشاء تصميم سيء للنظام منذ البداية.

الفكرة ليست في قدرتك على تنفيذ الاستعلامات، بل في التصميم نفسه. أي شيء يُبنى على أساس خاطئ سيؤدي إلى مشاكل أكبر في المستقبل.

النهج الصحيح: بناء خدمة بحث مستقلة ومرنة

لتحقيق متطلبات السرعة والمرونة، يجب التفكير في النظام من منظور قابلية التوسع (Scalability) والتجزئة (Partitioning). يجب أن يتكون النظام من خدمات متعددة تخدم أهدافًا محددة بناءً على منطق العمل، وهو توجه مستوحى من مفهوم التصميم الموجه بالمجال (Domain-Driven Design).

مكونات الحل المثالي

الحل الأفضل هو إنشاء خدمة بحث منفصلة تمامًا، تتكون من المكونات التالية:

  1. خدمة الفهرسة (Indexing Service): وظيفتها هي قراءة البيانات مباشرة من قاعدة البيانات المصدر (Source Database) وتنفيذ عملية ETL (Extract, Transform, Load).
    • Extract: استخراج البيانات من قاعدة بيانات المنتجات.
    • Transform: تحويل هيكل البيانات ليتناسب مع متطلبات البحث.
    • Load: تحميل البيانات المحوَّلة إلى قاعدة بيانات مخصصة للبحث.
  2. طوابير الرسائل (Message Queues) للاتصال غير المتزامن: بدلاً من الاتصال المباشر والمتزامن (Synchronous) الذي يتطلب انتظار الرد، نستخدم الاتصال غير المتزامن. عندما يكون لديك كميات هائلة من البيانات لنقلها (ملايين السجلات)، فإن طلب API واحد لن يكون فعالاً وقد يستغرق وقتًا طويلاً جدًا. الحل هو استخدام طابور رسائل (Queue) مثل Apache Kafka أو RabbitMQ أو خدمات سحابية مثل Amazon SQS. تقوم خدمة الفهرسة بنشر (Publish) رسالة تحتوي على بيانات المنتج المحوَّلة إلى الطابور. ثم تقوم خدمة أخرى بالاشتراك (Subscribe) في هذا الطابور وتستهلك (Consume) الرسائل لمعالجتها وتحميلها في قاعدة بيانات البحث. هذا النمط يضمن عدم فقدان البيانات إذا تعطلت إحدى الخدمات، ويتيح معالجة البيانات على دفعات (Batching)، ويزيد من قابلية التوسع.

  3. تحويل البيانات (Data Transformation): أثناء عملية الفهرسة، يتم تحويل هيكل بيانات المنتج ليصبح أكثر ملاءمة للبحث (Denormalized).

هيكل البيانات المحوَّل

قد يبدو هيكل بيانات البحث الجديد كالتالي:

{
  "search_id": "unique_search_id_123",
  "original_id": "ObjectId('...')",
  "title": {
    "en": "iPhone 14 Pro",
    "ar": "آيفون ١٤ برو"
  },
  "description": {
    "en": "The latest iPhone with Pro camera system.",
    "ar": "أحدث جهاز آيفون مع نظام كاميرا احترافي."
  },
  "category": "Smartphones",
  "brand": "Apple",
  "price": 1200,
  "currency": "USD",
  "thumbnail": "https://example.com/iphone14-front.jpg",
  "tags": ["apple", "iphone", "smartphone", "camera"],
  "sponsored": true,
  "created_at": "2025-07-26T10:00:00Z"
}

ملاحظات على التحويل:

  1. محرك البحث (Search Engine): البيانات المحوَّلة يتم تحميلها في قاعدة بيانات أو محرك بحث مُحسَّن لعمليات البحث السريعة. من الخيارات الممتازة:
    • Atlas Search: إذا كنت تستخدم MongoDB Atlas، فهو خيار رائع ومدمج يوفر تكلفة منخفضة.
    • Elasticsearch / OpenSearch: محركات بحث مستقلة وقوية جدًا، توفر ميزات متقدمة مثل البحث الضبابي (Fuzzy Search)، وترتيب النتائج بناءً على الأوزان (Search Weighting)، وتحليل الاستعلامات المعقدة.

بعد إعداد هذا المحرك، يتم توفير نقطة نهاية (Endpoint) عبر بروتوكول HTTP يمكن لأي واجهة (موبايل، ويب، خدمة أخرى) استخدامها للحصول على نتائج البحث بسرعة فائقة، دون التأثير إطلاقًا على خدمة الكتالوج الرئيسية.

الخلاصة: العقلية أهم من الأدوات

بهذا التصميم، نكون قد أنشأنا نظامًا مرنًا (Resilient)، وقابلاً للتطوير (Scalable)، وسهل الصيانة (Maintainable).

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

لا تتخيل أن هذه المهارات مخصصة فقط للمهندسين ذوي الخبرة العالية. السوق اليوم يتطلب من كل مهندس برمجيات، حتى المبتدئ، أن يفهم كيف سيؤثر عمله على النظام ككل. الأسئلة حول كيفية التعامل مع الفشل، وزمن الاستجابة، والمعاملات (Transactions) أصبحت أكثر أهمية من مجرد كتابة الشيفرة.

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

شارك المقال

أحدث المقالات

CONNECTED
ONLINE: ...
SECURE
00:00:00