Skip to main content
كل المقالات →
التجارة الإلكترونيةالمعماريةتعدد المستأجرين

كيف بنينا منصة تجارة إلكترونية متعددة المستأجرين

7 دقائق قراءة

تواجه الشركات الصغيرة التي تريد متجرًا إلكترونيًا الخيار نفسه غالبًا: أن تدفع مقابل بناء مخصص، أو أن تُحشر في قالب عام. سلكنا طريقًا ثالثًا. بنينا منصة تجارة واحدة: واجهة متجر ويب ولوحة تحكم للمتجر وتطبيق جوال، وجعلناها متعددة المستأجرين (multi-tenant)، فيُطلق متجر جديد بهويته الخاصة دون بناء جديد أو خادم جديد.

تعمل المنصة اليوم كسوق لبيع السيارات وشرائها، ويمكنك تجربة متجر تجريبي مباشر. تشرح هذه المقالة كيف تعمل وماذا تعلّمنا أثناء بنائها.

ما يحصل عليه صاحب المتجر

يحصل كل متجر على:

  • واجهة متجر على الويب باسم المتجر وشعاره وألوانه وأيقونته
  • لوحة تحكم لإدارة المنتجات والتصنيفات واللافتات والطلبات
  • تطبيق جوال (Flutter، لأندرويد وiOS)
  • محتوى بالعربية والإنجليزية والكردية في كل مكان

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

خادم واحد وقاعدة بيانات واحدة وعدة متاجر

هناك طريقتان لبناء منصة بالعلامة البيضاء:

  1. حزمة مستقلة لكل عميل. يحصل كل متجر على حاوية خادم وقاعدة بيانات خاصتين به. العزل بسيط، لكن كل عميل جديد يعني بنية تحتية أكثر تُجهَّز وتُحدَّث ويُدفع ثمنها.
  2. حزمة مشتركة بمفتاح مستأجر (tenant). تستخدم كل المتاجر الخادم وقاعدة البيانات نفسيهما، وينتمي كل سجل إلى مستأجر.

بدأنا بالخطة الأولى على الورق، واخترنا الثانية لمنصة الويب. التقنيات هي Node.js مع Express وPrisma وMySQL، وReact للواجهة الأمامية. يحمل كل جدول مرتبط بالمستأجر (المنتجات والتصنيفات واللافتات والطلبات والتقييمات وقوائم الرغبات وغيرها) عمود tenantId. ويُحدَّد المستأجر من اسم المضيف في الطلب، فيصل shop-name.example.com والنطاق الرئيسي للمنصة إلى الكود نفسه، ويريان بيانات مختلفة.

المقايضة واضحة: توفّر الكثير من العمل التشغيلي، وتتحمل مسؤولية ألا تسرّب بيانات مستأجر إلى آخر أبدًا.

الهوية وقت التشغيل لا وقت البناء

لو احتاج كل متجر إلى بناء خاص به لعدنا إلى النشر لكل عميل. لذلك تسأل واجهة المتجر الـ API عن المتجر الذي يشاهده الزائر. تُرجع الـ API اسم المستأجر وألوانه وشعاره وأيقونته، ويطبّقها التطبيق كمتغيرات CSS عند التحميل.

وهناك تفصيلان يجعلان ذلك آمنًا عمليًا:

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

العزل مسألة بنيوية

الدرس الأكبر في المشروع يتعلق بالعزل. كان نهجنا الأول أن نُرشِّح حسب المستأجر في كل متحكّم (controller). وهذا يعتمد على أن يتذكر كل مطوّر ذلك في كل مرة، ومرشِّح واحد منسيّ يعني تسريب بيانات.

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

ثم كتبنا اختبار عزل يعمل على قاعدة بيانات حقيقية، بمستأجرين اثنين، ويتحقق من أن أيًّا منهما لا يرى بيانات الآخر. وقد كشف خطأ حقيقيًا فاتت اختباراتِنا الوحداتِ المعتمدة على المحاكاة. فسياق المستأجر لم يكن يصل إلى طبقة قاعدة البيانات، بسبب تفاعل دقيق بين AsyncLocalStorage ودالة رد نداء تُرجع وعدًا (promise) بدل أن تكون دالة async. كان الإصلاح صغيرًا. أما الدرس فليس صغيرًا:

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

يعمل هذا الاختبار الآن حارسًا دائمًا، ونطبّق القاعدة نفسها على كل ما يتعلق بالمال.

الطلبات والمدفوعات

لا يكتمل المتجر دون إتمام الشراء. تضم المنصة سلة وطلبات وسجل طلبات للمتسوّقين، وقائمة طلبات لصاحب المتجر بحالات تمتد من قيد الانتظار إلى مؤكَّد إلى مُسلَّم.

وهناك عدة قرارات تستحق الذكر:

  • لا يُوثَق بالأسعار القادمة من العميل أبدًا. يُعيد إتمام الشراء حساب كل بند على الخادم من السعر الحالي للمنتج وخصمه.
  • تثبّت بنود الطلب السعر وقت الشراء، وتبقى حتى لو حُذف المنتج لاحقًا، لأن الطلب سجل تاريخي.
  • إتمام الشراء معاملة واحدة. تُنشئ الطلب وتكتب بنوده وتفرّغ السلة معًا، أو لا يحدث شيء.
  • الدفع عند الاستلام هو الافتراضي. وتستخدم مدفوعات البطاقة صفحة دفع مستضافة حيث يكون المتجر قد فعّلها. ولا يصبح الطلب "مدفوعًا" إلا حين يقول webhook موقّع ذلك، لا لأن المتصفح يقول إنه عاد من صفحة الدفع، فهذا التحويل يمكن تجاوزه أو تزويره.

الإطلاق: تجهيز المتاجر دون عناء

جعل المتجر متاحًا يتطلب DNS وشهادة TLS ومسارًا في خادم الويب. في البداية كان أحدهم يشغّل سكربتًا يدويًا بعد كل متجر جديد، وتعلمنا بالطريقة الصعبة أن "أحدهم سيتذكر" لا تصمد. أُنشئ متجر ولم تكن له شهادة، فرأى الزوار تحذيرًا أمنيًا في المتصفح.

الآن تعمل مهمة صغيرة على الخادم كل بضع دقائق، تبحث عن المتاجر النشطة التي لا تملك إعداد موقع، وتجهّزها. وهناك عدة اختيارات تُبقي ذلك آمنًا:

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

لوحة تحكم كاملة لكل متجر

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

ما ننصح به أي فريق آخر

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

تريد متجرًا خاصًا بك؟

تطلق SolidStack هذه المنصة بهوية نشاطك التجاري، باسمك وألوانك ونطاقك ولغاتك، وتشغّلها لك. شاهد المتجر التجريبي، أو اطّلع على خدماتنا، أو تواصل معنا.

تعمل على شيء مشابه؟

أخبرنا بما تبنيه وسنعود إليك.

تواصل معنا