وبلاگ

معماری چندمستاجری در محصولات SaaS

جداسازی داده، مدل نقش‌ها، قیدهای یکتایی و محدودیت پلن‌ها؛ تصمیم‌هایی که در محصول SaaS باید روز اول گرفته شوند چون تغییرشان بعداً گران است.

عکس رضا عبدالهی (Reza Abdollahi)، مهندس نرم‌افزار فول‌استک تیم NextApps
مهندس نرم‌افزار فول‌استک
۹ دقیقه مطالعه

در یک نرم‌افزار سفارشی، همه‌ی داده‌ها متعلق به یک کسب‌وکار است و کافی است مطمئن شوید کاربران داخل همان مجموعه به بخش‌های درست دسترسی دارند. در یک محصول SaaS ماجرا فرق می‌کند: ده‌ها کسب‌وکار مستقل روی یک سامانه‌ی واحد کار می‌کنند و کوچک‌ترین اشتباه در جداسازی داده یعنی نشت اطلاعات یک مشتری به مشتری دیگر.

این نوشته درباره‌ی تصمیم‌هایی است که در همان روزهای اول باید بگیرید، چون تغییرشان بعداً بسیار گران است.

مستاجر چیست و چرا مهم است

«مستاجر» (Tenant) یعنی یک مشتری از محصول شما — یک تعمیرگاه، یک سالن زیبایی، یک شرکت. هر مستاجر کاربران خودش، داده‌ی خودش و تنظیمات خودش را دارد و هیچ‌وقت نباید چیزی از مستاجر دیگر ببیند.

نکته‌ی مهم این است که مستاجر با کاربر یکی نیست. یک مستاجر می‌تواند ده کاربر با نقش‌های مختلف داشته باشد، و در برخی محصولات یک کاربر می‌تواند به چند مستاجر دسترسی داشته باشد. اگر این دو مفهوم را از اول جدا نکنید، مدل داده‌تان خیلی زود گره می‌خورد.

سه راه برای جداسازی داده

پایگاه داده‌ی جدا برای هر مستاجر

بیشترین جداسازی را دارد: هر مشتری پایگاه داده‌ی خودش را دارد. پشتیبان‌گیری و بازگردانی برای یک مشتری خاص ساده است و هیچ کوئری‌ای نمی‌تواند از مرز عبور کند.

هزینه‌اش هم روشن است: اجرای یک مهاجرت (migration) روی صد پایگاه داده، اتصال‌های همزمان، و پیچیدگی عملیاتی. این گزینه وقتی منطقی است که تعداد مشتری‌ها کم و بزرگ باشند یا الزام قانونی برای تفکیک کامل داشته باشید.

اسکیمای جدا در یک پایگاه داده

حد وسط. یک پایگاه داده، اما هر مستاجر اسکیمای خودش را دارد. جداسازی هنوز در سطح پایگاه داده اعمال می‌شود اما مدیریت ساده‌تر از حالت قبل است. مهاجرت‌ها همچنان باید روی همه‌ی اسکیماها اجرا شوند.

ستون tenantId در جدول‌های مشترک

رایج‌ترین و در بیشتر محصولات درست‌ترین گزینه: یک پایگاه داده، یک اسکیما، و هر ردیف یک tenantId دارد. مهاجرت یک‌بار اجرا می‌شود، عملیات ساده است و مقیاس‌پذیری خوبی دارد.

خطرش هم واضح است: کافی است در یک کوئری شرط tenantId را فراموش کنید تا داده‌ی یک مشتری به مشتری دیگر نشان داده شود.

اگر گزینه‌ی سوم را انتخاب کردید، این را فراموش نکنید

نکته‌ی کلیدی این است: جداسازی نباید به یادآوری برنامه‌نویس وابسته باشد. روزی که پروژه بیست جدول و پنجاه کوئری دارد، «یادمان باشد tenantId را بگذاریم» دیگر یک راهبرد نیست.

جداسازی باید در پایین‌ترین لایه‌ی ممکن اعمال شود:

  • در PostgreSQL از Row Level Security استفاده کنید. با تعریف policy روی هر جدول، پایگاه داده خودش ردیف‌های خارج از مستاجر جاری را برمی‌گرداند — حتی اگر کوئری شرط را نداشته باشد.
  • اگر از ORM استفاده می‌کنید، مستاجر را در همان لایه‌ای تزریق کنید که اتصال ساخته می‌شود، نه در هر فراخوانی.
  • tenantId را از توکن کاربر بخوانید، هرگز از پارامتر درخواست. اگر شناسه‌ی مستاجر از سمت کلاینت بیاید، هر کاربری می‌تواند آن را عوض کند و داده‌ی دیگری را بخواند.

نقش‌ها در دو سطح

در محصولات SaaS معمولاً دو سطح نقش دارید و قاطی‌کردنشان اشتباه پرهزینه‌ای است:

نقش‌های داخل مستاجر — مدیر، پذیرش، تکنسین. اینها مشخص می‌کنند یک کاربر داخل کسب‌وکار خودش چه کاری می‌تواند بکند.

نقش‌های سطح پلتفرم — تیم خودتان که به پنل مدیریت پلتفرم دسترسی دارد و وضعیت همه‌ی مستاجرها را می‌بیند.

این دو باید کاملاً جدا باشند. یک «مدیر» در سطح مستاجر نباید هیچ مسیری برای رسیدن به داده‌ی سطح پلتفرم داشته باشد.

کلیدهای یکتا را با مستاجر بسنجید

اشتباهی که در همه‌ی پروژه‌های چندمستاجری تکرار می‌شود: گذاشتن قید یکتایی روی یک ستون به‌تنهایی.

شماره‌ی فاکتور نباید در کل سامانه یکتا باشد؛ باید در محدوده‌ی هر مستاجر یکتا باشد. وگرنه اولین مشتری فاکتور شماره‌ی ۱ را می‌سازد و مشتری دوم نمی‌تواند فاکتور شماره‌ی ۱ خودش را ثبت کند. همین برای کد محصول، شماره‌ی سفارش و نام کاربری داخلی هم صدق می‌کند. قید یکتایی باید ترکیبی باشد: (tenantId, invoiceNumber).

اشتراک و محدودیت پلن‌ها

بخشی از محصول که معمولاً دیر به آن فکر می‌شود اما دیر اضافه‌کردنش سخت است. از ابتدا مشخص کنید:

  • هر پلن چه محدودیت‌هایی دارد (تعداد کاربر، تعداد رکورد، حجم فایل).
  • وقتی اشتراک تمام می‌شود چه اتفاقی می‌افتد: دسترسی قطع می‌شود یا فقط حالت خواندنی می‌ماند؟ داده تا کی نگه داشته می‌شود؟
  • محدودیت‌ها کجا بررسی می‌شوند. اگر فقط در رابط کاربری چک شوند، با یک درخواست مستقیم به API دور زده می‌شوند.

تست‌هایی که واقعاً لازم‌اند

برای یک محصول چندمستاجری، مهم‌ترین تست‌ها تست‌های نشت داده‌اند. یک الگوی ساده که ارزش زیادی دارد: برای هر منبع API، یک تست بنویسید که با توکن مستاجر «الف» به شناسه‌ی یک رکورد متعلق به مستاجر «ب» درخواست می‌دهد و انتظار پاسخ ۴۰۴ دارد.

این تست ساده است اما همان دسته اشکالی را می‌گیرد که گران‌ترین اشکال‌های این نوع محصول‌اند.

جمع‌بندی

معماری چندمستاجری تصمیمی نیست که بتوانید به بعد موکول کنید. جداسازی داده، مدل نقش‌ها و قیدهای یکتایی از روز اول باید درست باشند، چون بعد از اینکه چند مشتری واقعی روی سامانه داده وارد کردند، تغییرشان یعنی مهاجرت داده و ریسک.

خبر خوب اینکه اگر همین چند مورد را از ابتدا رعایت کنید، بقیه‌ی محصول تفاوت چندانی با یک نرم‌افزار معمولی ندارد.

برچسب‌ها:SaaSمعماری نرم‌افزارPostgreSQL

مقالات دیگر

چک‌لیست سئوی فنی برای سایت‌های Next.js

هفت کاری که روی هر پروژه‌ی Next.js انجام می‌دهیم تا سایت از روز اول برای موتورهای جستجو آماده باشد: متادیتای هر صفحه، canonical، نقشه‌ی سایت، داده‌ی ساختاریافته و سرعت.

آماده‌ی شروع

ایده‌ای دارید که ارزش ساخته شدن دارد؟

بیایید آن را به یک محصول واقعی تبدیل کنیم.