العربية
  • saas
  • تعدد المستأجرين
  • postgres
  • أمان مستوى الصف
  • rls
  • وظيفة الزناد
  • بنية تعدد المستأجرين
  • بنية المستأجر الواحد

تنفيذ تعدد المستأجرين مع PostgreSQL: تعلم من خلال مثال بسيط في العالم الحقيقي

تعلم كيفية تنفيذ بنية تعدد المستأجرين مع PostgreSQL أمان مستوى الصف (RLS) وأدوار قاعدة البيانات من خلال مثال في العالم الحقيقي لضمان عزل البيانات الآمن بين المستأجرين.

Yijun
Yijun
Developer

في بعض مقالاتنا السابقة، استكشفنا مفهوم تعدد المستأجرين وتطبيقاته في المنتجات والسيناريوهات العملية في الأعمال.

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

ما هي بنية المستأجر الواحد؟

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

في هذه البنية، يتم عزل بيانات وموارد كل مستأجر تمامًا عن المستأجرين الآخرين.

دفعة واحدة

ما هي بنية تعدد المستأجرين؟

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

تعددية المستأجرين

بنية المستأجر الواحد مقابل بنية تعدد المستأجرين

تختلف بنية المستأجر الواحد وبنية تعدد المستأجرين في جوانب مثل عزل البيانات واستغلال الموارد وقابلية التوسع والإدارة والصيانة والأمان.

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

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

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

استراتيجيات عزل بيانات المستأجرين في بنية تعدد المستأجرين

في بنية تعدد المستأجرين، يشارك جميع المستأجرين نفس الموارد الأساسية، مما يجعل عزل الموارد بين المستأجرين أمرًا حيويًا. لا يتعين أن يكون هذا العزل ماديًا؛ بل يتطلب ببساطة التأكد من أن الموارد بين المستأجرين غير مرئية لبعضها البعض.

في تصميم البنية، يمكن تحقيق عدة درجات من عزل الموارد بين المستأجرين:

معزولة إلى مشتركة

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

بدء تنفيذ تعدد المستأجرين بمثال عملي

في هذه المقالة، سنستخدم نظام إدارة علاقات العملاء (CRM) كمثال لتقديم بنية تعدد مستأجرين بسيطة وعملية.

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

بالإضافة إلى ذلك، سنقوم بإنشاء اتصال بيانات منفصل لكل مستأجر لتسهيل إدارة أذونات المستأجرين بشكل أفضل.

التالي، سنقدم كيفية تنفيذ هذه البنية المتعددة المستأجرين.

كيفية تنفيذ بنية تعدد المستأجرين مع PostgreSQL

إضافة معرف المستأجر لجميع الموارد

في نظام CRM، سنحتفظ بالعديد من الموارد ويتم تخزينها في جداول مختلفة. على سبيل المثال، يتم تخزين معلومات العملاء في جدول customers.

قبل تنفيذ تعددية، هذه الموارد غير مرتبطة بأي مستأجر:

لتمييز المستأجرين الذين يمتلكون موارد مختلفة، نقدم جدول tenants لتخزين معلومات المستأجر (حيث يستخدم db_user و db_user_password لتخزين معلومات اتصال قاعدة البيانات لكل مستأجر، سيتم تفصيلها أدناه). بالإضافة إلى ذلك، نضيف حقل tenant_id لكل مورد لتحديد المستأجر الذي ينتمي إليه:

الآن، يرتبط كل مورد بـ tenant_id، مما يمكننا نظريًا من إضافة جملة where لجميع الاستعلامات لتقييد الوصول إلى الموارد لكل مستأجر:

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

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

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

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

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

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

ثم، لتمييز كل دور للمستأجر، يتم تعيين دور موروث من الدور الأساسي لكل مستأجر عند إنشائه:

التالي، سيتم تخزين معلومات اتصال قاعدة البيانات لكل مستأجر في جدول tenants:

iddb_userdb_user_password
x2euiccrm_tenant_x2euicpa55w0rd

هذا الآلية توفر لكل مستأجر دوره الخاص بقاعدة البيانات، وهذه الأدوار تشارك الصلاحيات الممنوحة لدور crm_tenant.

يمكننا بعد ذلك تحديد نطاق الصلاحيات للمستأجرين باستخدام دور crm_tenant:

  • يجب أن يكون للمستأجرين صلاحيات CRUD للوصول إلى جميع جداول موارد النظام CRM.
  • الجداول التي لا تتعلق بموارد نظام CRM يجب أن تكون غير مرئية للمستأجرين (نُفترض فقط جدول systems).
  • يجب ألا يحصل المستأجرون على إمكانية تعديل جدول tenants، ويجب أن يكون فقط id و db_user مرئيين لهم لاستعلام معرف المستأجر الخاص بهم عند القيام بعمليات قاعدة البيانات.

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

تأمين بيانات المستأجر باستخدام أمان مستوى الصف في PostgreSQL

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

في PostgreSQL، يمكن للجداول أن تحتوي على سياسات أمان الصفوف التي تتحكم في أي الصفوف يمكن الوصول إليها من خلال الاستعلامات أو تعديلها عبر أوامر معالجة البيانات. تُعرف هذه الميزة أيضًا بـ RLS (أمان مستوى الصف).

بشكل افتراضي، لا تحتوي الجداول على أي سياسات أمان صفوف. للاستفادة من RLS، تحتاج إلى تمكينها للجداول وإنشاء سياسات الأمان التي تُنفَّذ كلما تم الوصول إلى الجدول.

باستخدام جدول customers في نظام CRM كمثال، سنقوم بتمكين RLS وخلق سياسة أمان لتقييد وصول كل مستأجر فقط إلى بيانات العملاء الشخصية الخاصة بهم:

في البيان الذي ينشئ سياسة الأمان:

  • for all (اختياري) يدل على أن سياسة الوصول هذه ستُستخدم لعمليات select و insert و update و delete على الجدول. يمكنك تحديد سياسة وصول لأوامر محددة باستخدام for تتبعها كلمة الأمر.
  • to crm_tenant يشير إلى أن هذه السياسة تنطبق على المستخدمين الذين لديهم دور قاعدة البيانات crm_tenant، بعبارة أخرى، على جميع المستأجرين.
  • as restrictive يحدد وضع تنفيذ السياسة، مما يشير إلى أن الوصول يجب أن يكون محدودًا بصرامة. افتراضيًا، يمكن للجداول أن تحتوي على سياسات متعددة، ستُجمع السياسات المتعددة permissive بعلاقة OR. في هذا السيناريو، نُعلن هذه السياسة كـ restrictive لأننا نريد أن تكون هذه السياسة حاجة إلزامية للمستخدمين المنتمين إلى مستأجري نظام CRM.
  • تعبير using يحدد الشروط للوصول الفعلي، مما يقيِّد المستخدم الحالي الذي يستفسر عن قاعدة البيانات ليشاهد فقط البيانات التي تخص المستأجر المعني. ينطبق هذا القيد على الصفوف التي يتم تحديدها بواسطة أمر (select أو update أو delete).
  • تعبير with check يحدد القيد الضروري عند تعديل صفوف البيانات (insert أو update)، مما يضمن أن المستأجرين يمكنهم فقط إضافة أو تحديث سجلات لأنفسهم.

باستخدام RLS لتقييد وصول المستأجرين إلى جداول الموارد الخاصة بنا يوفر عدة فوائد:

  • تضيف هذه السياسة فعليًا where tenant_id = (select id from tenants where db_user = current_user) إلى جميع عمليات الاستعلام (select أو update أو delete). على سبيل المثال، عندما تنفذ select * from customers، فإنه يساوي تنفيذ select * from customers where tenant_id = (select id from tenants where db_user = current_user). هذا يلغي الحاجة إلى إضافة شروط where بوضوح في شفرة التطبيق، مما يبسطها ويقلل من احتمال حدوث أخطاء.
  • التحكم في مستوى نظام قاعدة البيانات مركزيًا وصول البيانات بين المستأجرين المختلفين، مما يقلل من مخاطر نقاط الضعف أو التناقضات في التطبيق، مما يعزز من أمان النظام.

ومع ذلك، هناك بعض النقاط التي يجب الانتباه إليها:

  • تُنفَّذ سياسات RLS لكل صف من البيانات. إذا كانت شروط الاستعلام داخل سياسة RLS معقدة جدًا، يمكن أن تؤثر بشكل كبير على أداء النظام. لحسن الحظ، فإن استعلام التحقق الخاص ببيانات المستأجر بسيطة بما فيه الكفاية ولن تؤثر على الأداء. إذا كنت تخطط لتنفيذ وظائف أخرى باستخدام RLS لاحقًا، يمكنك اتباع توصيات أداء أمان مستوى الصف من Supabase لتحسين أداء RLS.
  • لا تُعلَن سياسات RLS تلقائيًا tenant_id خلال عمليات insert. حيث تقيِّد فقط المستأجرين بإدخال بياناتهم الخاصة. هذا يعني أنه عند إدراج البيانات، لا يزال علينا توفير معرف المستأجر، مما يكون غير متسق مع عملية الاستعلام ويمكن أن يؤدي إلى الارتباك خلال التطوير، مما يزيد من احتمال حدوث أخطاء (سيتم معالجة هذا في الخطوات في المستقبل).

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

إنشاء وظيفة الزناد لإدراج البيانات

كما ذُكر سابقًا، يسمح لنا RLS (أمان مستوى الصف) بتنفيذ الاستعلامات دون القلق من وجود tenant_id، حيث يتعامل قاعدة البيانات مع ذلك تلقائيًا. ومع ذلك، بالنسبة لعمليات insert، لا يزال علينا يدويًا تحديد tenant_id المعني.

لتحقيق نفس الراحة كما في RLS لإدراج البيانات، نحتاج إلى أن تتعامل قاعدة البيانات مع tenant_id تلقائيًا خلال إدراج البيانات.

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

لحسن الحظ، يوفر PostgreSQL وظيفة الزناد القوية.

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

أولا، دعونا ننشئ وظيفة زناد set_tenant_id لتنفيذها قبل إدراج كل بيانات جديدة:

التالي، اربط هذه الوظيفة بجدول customers لعمليات الإدراج (مشابه لتمكين RLS لجدول، تحتاج وظيفة الزناد إلى الارتباط بكل الجداول المعنية):

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

بهذه الطريقة، نحقق معالجة tenant_id تلقائيًا على مستوى قاعدة البيانات خلال إدراج البيانات بواسطة المستأجرين.

الخلاصة

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

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

هذا هو كل شيء لهذه المقالة. إذا كنت ترغب في تعزيز تطبيق تعدد المستأجرين الخاص بك مع إدارة وصول المستخدمين، يمكنك الرجوع إلى دليل سهل للبدء مع مؤسسات Logto - لبناء تطبيق متعدد المستأجرين لمزيد من الأفكار.