Get a quote

نقل سياق المستأجر في خوادم Go للـ SaaS: من Middleware إلى Repository

في خادم SaaS متعدد المستأجرين، هوية المستأجر يجب أن تنتقل مع كل طلب من طبقة HTTP Middleware عبر منطق الخدمة وصولاً إلى استعلام قاعدة البيانات. فوات حد واحد يسمح لمستأجر بقراءة بيانات مستأجر آخر.

في خادم SaaS متعدد المستأجرين، هوية المستأجر يجب أن تنتقل مع كل طلب من طبقة HTTP middleware عبر منطق الخدمة وصولاً إلى استعلام قاعدة البيانات. فوات حد واحد يسمح لمستأجر بقراءة بيانات مستأجر آخر. هذه هي البنية التي نستخدمها لنقل سياق المستأجر بأمان في خوادم Go للـ SaaS في الإنتاج.

المشكلة مع حل هوية المستأجر بشكل ضمني

خطأ شائع في الخوادم متعددة المستأجرين في مرحلة مبكرة هو تمرير tenantID كمعامل دالة عبر جميع طبقات التطبيق. هذا يبدو آمناً لكنه يخلق مشاكل عملية: سهل أن يمرر المطور tenant ID خاطئ، وعند عمق الاستدعاءات يُسقط المطورون المعامل.

استخدام Go context لحمل هوية المستأجر

النهج الصحيح هو تخزين tenant ID المُوثَّق في context.Context في طبقة HTTP middleware واسترجاعه في طبقة Repository:

func TenantMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        claims, ok := jwtClaimsFromContext(r.Context())
        if !ok {
            http.Error(w, "unauthorized", http.StatusUnauthorized)
            return
        }
        tenant := TenantContext{ID: claims.TenantID}
        ctx := context.WithValue(r.Context(), tenantContextKey, tenant)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

طبقة Repository تستدعي TenantFromContext قبل أي استعلام قاعدة بيانات. إذا كان سياق المستأجر مفقوداً، يُرجع Repository خطأ بدلاً من جلب جميع السجلات.

التعامل مع Goroutines في الخلفية

مكان شائع يضيع فيه سياق المستأجر هو عندما يُنشئ المعالج goroutine في الخلفية. الحل الصحيح هو نسخ هوية المستأجر من سياق الطلب وإنشاء سياق جديد:

tenant, _ := TenantFromContext(r.Context())
go func() {
    bgCtx := context.Background()
    bgCtx = context.WithValue(bgCtx, tenantContextKey, tenant)
    processOrderAsync(bgCtx, orderID)
}()

Row-Level Security كطبقة دفاع معمقة

نقل السياق يتعامل مع العزل في طبقة التطبيق. PostgreSQL Row-Level Security يوفر طبقة عزل ثانية في طبقة قاعدة البيانات. حتى لو وجد خطأ في طبقة التطبيق وتم تمرير tenant_id خاطئ إلى استعلام، PostgreSQL سيصفي النتيجة لتشمل فقط الصفوف المطابقة للمستأجر المحدد في الجلسة.

اختبار عزل المستأجرين

أخطاء عزل المستأجرين من أخطر الأخطاء في نظام SaaS. لكل طريقة Repository، اكتب اختباراً:

  1. ينشئ سجلات للمستأجر A والمستأجر B
  2. يستعلم بسياق المستأجر A
  3. يؤكد عدم ظهور سجلات المستأجر B في النتيجة

التسجيل والتتبع مع سياق المستأجر

يجب أن يتضمن التسجيل المنظم tenant ID في كل سطر سجل يُصدر خلال طلب. عند وصول تقرير خطأ من عميل محدد (شائع في SaaS الشرق الأوسط حيث يُدار الدعم مباشرة من الفريق المؤسس)، يمكنك تصفية السجلات بـ tenant_slug لرؤية كل عملية أجراها ذلك العميل.

دروس من الإنتاج

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

استخدم context للنقل، وليس معاملات الدوال. معاملات الدوال تُسقط مع عمق مكدس الاستدعاءات.

RLS هو طبقة دفاع معمق وليس بديلاً عن العزل في طبقة التطبيق.

هل تحتاج مساعدة في بناء نظام SaaS متعدد المستأجرين؟

Voxire تبني خوادم SaaS متعددة المستأجرين بـ Go للشركات في لبنان ومنطقة الشرق الأوسط. إذا كنت تصمم نظاماً متعدد المستأجرين أو تستكشف مشاكل العزل في خادم موجود، يمكننا المساعدة في البنية.

https://voxire.com/get-a-quote/

العودة إلى المدونة
Chat on WhatsApp