Get a quote

SQL الآمن من حيث الأنواع وgRPC في خلفيات Go للإنتاج: كيف يقلل sqlc وProtobuf أخطاء وقت التشغيل

خلفية Go في الإنتاج لستة أشهر تكاد تحتوي دائماً على فئتين متكررتين من الأخطاء: استعلامات SQL تنكسر عند تغيير عمود، واستدعاءات خدمات داخلية تفشل لأن أحدهم غيّر شكل الاستجابة دون تحديث المُستدعي. sqlc وprotobuf يحلان كليهما في وقت الترجمة لا في وقت التشغيل.

خلفية Go التي عملت في الإنتاج لستة أشهر تحتوي شبه دائماً على فئتين متكررتين من الأخطاء: استعلامات SQL تنكسر عند إعادة تسمية عمود في المخطط، واستدعاءات خدمات داخلية تفشل لأن شخصاً ما غيّر شكل الاستجابة دون تحديث كل من يستدعيها. sqlc وprotobuf يحلان كلتا المشكلتين في وقت الترجمة بدلاً من وقت التشغيل.

لماذا تُصبح استعلامات SQL المكتوبة كنصوص ثابتة في Go مشكلة في الإنتاج؟

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

المشكلة الثانية هي ترتيب أعمدة الـ Scan. استدعاء rows.Scan يجب أن يتطابق مع ترتيب أعمدة الـ SELECT تماماً. إعادة ترتيب الأعمدة لا تُنتج تحذيراً من المُترجم، بل تُنتج تعطلاً في وقت التشغيل أو تلفاً صامتاً في البيانات.

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

كيف يعمل sqlc وماذا يولّد؟

sqlc يأخذ ثلاثة مدخلات: ملف إعداد YAML، ملفات مخطط SQL، وملفات استعلام SQL. ثم يُنتج كود Go مكتوب بأنواع محددة: هياكل بيانات تتطابق مع جداول قاعدة البيانات، ودوال مكتوبة لكل استعلام مُسمّى.

يعرّف ملف الاستعلام الاستعلامات المُسمّاة:

-- name: GetTenantUsers :many
SELECT id, email, created_at
FROM users
WHERE tenant_id = @tenant_id
ORDER BY created_at DESC
LIMIT @limit;

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

تنظيم sqlc لخلفية SaaS متعددة المستأجرين

في خلفية SaaS متعددة المستأجرين، يجعل sqlc عزل المستأجرين قابلاً للتدقيق. كل استعلام يُرجع بيانات يجب أن يتضمن tenant_id في جملة WHERE. باستخدام نصوص SQL خام، يُفرض هذا فقط من خلال مراجعة الكود. باستخدام sqlc، توقيعات الدوال المُولَّدة تُوضح للوهلة الأولى ما إذا كانت معامل tenant_id موجودة.

نمط الفلترة المزدوج لعزل صف المستأجر:

-- name: GetOrder :one
SELECT id, tenant_id, status, total_cents, created_at
FROM orders
WHERE id = @id AND tenant_id = @tenant_id;

كلا الفلترين مطلوبان في توقيع الدالة المُولَّدة. المعالج الذي يُمرر tenant_id خاطئاً لا يحصل على نتيجة بدلاً من تسريب بيانات مستأجر آخر.

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

متى يكون gRPC مناسباً ومتى تختار REST؟

gRPC يستحق تكلفة الإعداد عندما تتواصل الخدمات بتردد عالٍ، أو عندما تريد فرض العقود في وقت الترجمة عبر حدود الخدمات، أو عندما يكون لديك مستهلكون متعددون للواجهة البرمجية الداخلية بلغات مختلفة.

لمعظم منتجات SaaS في لبنان والمنطقة في المراحل الأولى من التوسع، يخدم REST مع مواصفات OpenAPI التواصل الداخلي بشكل أفضل حتى يكون لديك ثلاث خدمات أو أكثر تتصل ببعضها بشكل متكرر.

الحالات التي يؤتي فيها gRPC ثماره بشكل موثوق:

  • خدمات المعالجة التي تُعالج عناصر قائمة الانتظار وتتصل بواجهة برمجية مركزية بتردد عالٍ
  • خلفيات أجهزة نقاط البيع التي تتصل بخدمة مزامنة مركزية باستخدام بث ثنائي الاتجاه
  • خدمات تجميع التقارير التي تستعلم من خدمات داخلية متعددة

تصميم مخططات Protobuf للخدمات الداخلية

رسائل Protobuf متوافقة للأمام: إضافة حقول لا يُعطّل المستهلكين الحاليين، إزالة الحقول التي يستخدمونها تُعطّلهم. هذا التفاوت يُشكّل طريقة تصميم ملفات proto.

الممارسات الرئيسية:

  • استخدم string للمعرّفات والطوابع الزمنية
  • استخدم int64 للقيم النقدية بأصغر وحدة (قروش، فلوس) بدلاً من الأعداد العشرية
  • رقّم حزمك من اليوم الأول: orders.v1 وليس orders

عندما يكون تغيير جوهري ضرورياً، أضف orders.v2 وانقل المستهلكين. اللاحقة الرقمية تجعل العمل بإصدارين معاً خلال فترة الانتقال واضحاً ومرئياً في قاعدة الكود.

تشغيل توليد الكود في CI

yجب أن يعمل توليد كود كل من sqlc وprotobuf كجزء من خط CI، وليس كخطوة يدوية. الكود المُولَّد يجب تسجيله في المستودع. CI يتحقق من أن الكود المُسجَّل يتطابق مع ما سيُولَّد من المخطط الحالي وملفات proto.

إذا كان الكود المُولَّد غير متزامن مع المخطط، تفشل الخطوة. هذا يمنع حالة الفشل الشائعة حيث يُحدّث مهندس ملف مخطط دون إعادة توليد الكود.

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

sqlc يُلغي بالكامل نوع الأخطاء المرتبطة بترتيب الـ Scan ويجعل عزل المستأجرين قابلاً للتدقيق في مراجعة الكود. اعتمده عندما ينتقل المشروع من مرحلة النموذج الأولي.

gRPC يؤتي ثماره للتواصل الداخلي عالي التردد والبث ثنائي الاتجاه. بالنسبة للواجهات البرمجية التي يصل إليها REST، التزم بـ OpenAPI.

رقّم حزم proto من اليوم الأول. الانتقال من proto غير مُرقَّم في منتصف المشروع أكثر إرباكاً من إضافة اللاحقة من البداية.

نفّذ توليد الكود في CI وسجّل النتيجة. الانحراف بين المخطط والكود المُولَّد مشكلة في الإنتاج يجب أن يكتشفها CI.

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