نظرة عامة

Sham منتج منصّة مبنيّ على نظام خلفي بـ Django + Django REST Framework يعرض واجهة JSON برمجية واحدة لأكثر من مستهلك. وإلى جانب هذه الواجهة يعيش sham_mobile، عميل جوال مرافق يتحدّث إلى نقاط الوصول ذاتها التي يستخدمها سطح الويب. والنظام كلّه يعمل داخل حاويات، تتقدّمه Nginx، ويُشحن عبر مسار GitLab CI، مع مجلّد نسخ احتياطي محفوظ داخل شجرة المستودع بحيث تكون الاستعادة اهتمامًا من الدرجة الأولى لا فكرة لاحقة.

التحدي

المنتجات التي يَنبت لها عميل جوال بعد تطبيق الويب عادةً ما يَنبت معها نظام خلفي ثانٍ — طبقة BFF رفيعة، أو مجموعة نقاط وصول خاصة بالجوال، أو الأسوأ: مجموعة موازية من المُسلسِلات تنحرف عن الأصلية خلال إصدار أو إصدارين. عندئذٍ يصير لكل قاعدة عمل موطنان، ويتحوّل كل تغيير في المخطّط إلى مشكلة تنسيق بين قاعدتَي كود ووتيرتَي إصدار. وقد احتاج Sham إلى النقيض: واجهة برمجة واحدة مرجعية، وقصّة مصادقة واحدة، ومسار وسائط واحد، ومُخرَج نشر واحد، مع التعامل مع عميل الجوال بوصفه مجرّد مستهلك موثَّق آخر للعقد نفسه. وكان القيد هو إبقاء ذلك السطح صغيرًا بما يكفي لاستيعابه ذهنيًا، مع تغطية ما تحتاجه المنتجات الحقيقية فعلًا — مصادقة قائمة على الرموز لعملاء عديمي الحالة، ورفع الصور ومعالجتها، وتصدير جدولي وبصيغة PDF، وتسليم الأصول الثابتة، ومسار قابل للتكرار من commit إلى حاوية تعمل.

تصميم النظام

النظام الخلفي مشروع Django 5 مع عزل طبقة REST داخل تطبيق api واحد، بما يُبقي النماذج والمُسلسِلات وviewsets وتوجيه المسارات في مكان واحد بدل تشتّتها عبر اثني عشر تطبيقًا نصف ممتلئ. وتعمل المصادقة على djangorestframework-simplejwt، وهو ما يناسب شكل المشكلة: فعميل الجوال لا يملك حاوية كوكيز ولا انتماء جلسة يتّكئ عليهما، ولذلك يتيح حمل رمزَي الوصول والتحديث في ترويسة Authorization للعميلين أن يصادقا عبر مسار الكود نفسه تمامًا. ويقف django-cors-headers أمام ذلك ليتيح للسطح العامل في المتصفّح إصدار طلبات عابرة للأصل نحو مضيف الواجهة البرمجية دون التساهل في أي شيء تجاه المستدعين الآخرين.

يمرّ التعامل مع الوسائط عبر Pillow، وهو ما يلجأ إليه حقل ImageField في Django حين يحتاج إلى التحقّق من الملفات المرفوعة ومعالجتها — فحص الأبعاد، والتأكّد من الصيغة، وأي تغيير للحجم يجري في الطريق إلى التخزين. أما الأصول الثابتة فيقدّمها whitenoise من داخل عملية التطبيق نفسها، ما يعني أن الحاوية مكتفية ذاتيًا فيما يخصّ هذه الأصول، ويترك Nginx حرًّا ليفعل ما يُتقنه فعلًا: إنهاء الاتصالات، وتقديم الوسائط التي يرفعها المستخدمون من القرص، وتمرير كل ما عدا ذلك عبر وكيل عكسي إلى gunicorn. وتُخرَج التهيئة إلى الخارج عبر python-dotenv مقابل ملف .env.example محفوظ في المستودع، فتعمل الصورة ذاتها في كل بيئة دون أن يكون مخبوزًا داخلها شيء سوى الكود. وطبقة الحفظ الافتراضية هي SQLite (db.sqlite3)، ما يُبقي التطوير المحلي عند أمر manage.py migrate واحد دون أي اعتماديات على خدمات.

ويُعالَج اهتمامان تشغيليان بوصفهما كودًا من الدرجة الأولى لا معرفة شفهية متوارثة. فالتقارير مبنيّة على openpyxl لمخرجات جداول البيانات وعلى reportlab لتوليد ملفات PDF، وكلاهما يُدار من جانب الخادم بحيث يستطيع عميل الجوال طلب مستند دون أن يشحن معه محرّك تصيير. والتهيئة الأولية مكتوبة في سكربتات: يُنشئ create_admin.py المستخدم الخارق الأوّل دون تفاعل، بينما يزرع setup_categories.py تصنيف الفئات الذي يعتمد عليه المنتج — سكربتان يحوّلان حاوية جديدة إلى نسخة قابلة للاستخدام دون أن ينقر أحد داخل لوحة الإدارة. والتسليم معرَّف عبر Dockerfile، وdocker-compose.yml الذي يربط التطبيق بإعدادات nginx، و.gitlab-ci.yml الذي يقود المسار، فيما يحمل backups/ أدوات الاستعادة.

كيف يعمل

01/04
01

مصادقة العملاء القائمة على الرموز

يحصل كلٌّ من سطح الويب وعميل sham_mobile على رموز JWT ويجدّدها عبر مسار المصادقة نفسه في DRF.

  1. 1يُرسل عميلٌ بيانات الاعتماد إلى نقطة وصول الرموز في SimpleJWT التي يعرضها تطبيق api
  2. 2يتحقّق النظام الخلفي منها عبر واجهة المصادقة الخلفية في Django، ويعيد رمز وصول قصير العمر ورمز تحديث أطول عمرًا
  3. 3تحمل استدعاءات الواجهة البرمجية اللاحقة رمز الوصول في ترويسة Authorization: Bearer، ويحوّله DRF إلى request.user قبل أي فحص للصلاحيات
  4. 4وعند انتهاء صلاحية رمز الوصول، يستبدل العميل رمز التحديث برمز جديد بدل مطالبة المستخدم ببيانات الاعتماد من جديد
  5. 5وتجتاز الطلبات القادمة من أصل المتصفّح إضافةً إلى ذلك فحص ما قبل الطلب في django-cors-headers؛ أما عميل الجوال فلا أصل له ويصل إلى العرض ذاته
02

رفع الصور وتسليم الوسائط

يتحقّق Pillow من الصور المرفوعة قبل تخزينها، ثم يقدّمها Nginx بعد ذلك مباشرةً من وحدة تخزين مركّبة.

  1. 1يُرسل عميل طلبًا بصيغة multipart إلى مُسلسِل في DRF مدعوم بحقل ImageField في Django
  2. 2يفتح Pillow الملف المرفوع للتأكّد من أنه صورة حقيقية ولقراءة صيغته وأبعاده، رافضًا أي ملف مشوّه قبل أن يصل إلى النموذج
  3. 3يُكتب الملف بعد التحقّق منه في جذر الوسائط، وهو وحدة تخزين مركّبة داخل الحاوية بحيث تبقى الملفات المرفوعة بعد استبدال الصورة
  4. 4وعند القراءة، يقدّم Nginx الملف مباشرةً من وحدة التخزين تلك دون إيقاظ عملية Python
03

تصدير المستندات من جانب الخادم

تُولَّد ملفات جداول البيانات وملفات PDF على الخادم، فلا يحتاج أيٌّ من العميلين إلى محرّك تصيير.

  1. 1يطلب عميل تصديرًا من نقطة وصول في الواجهة البرمجية، محدّد النطاق بالصلاحيات المستخلَصة سلفًا من رمز JWT الخاص به
  2. 2يستعلم العرض عن السجلّات المعنيّة ويسلّمها إلى مولّد
  3. 3للمخرجات الجدولية، يبني openpyxl مصنّفًا في الذاكرة؛ وللمخرجات الجاهزة للطباعة، يُركّب reportlab ملف PDF
  4. 4تُبثّ النتيجة عائدةً في استجابة HTTP بنوع المحتوى وطريقة العرض المناسبين، فيحصل العميلان على ملف متطابق
04

البناء والنشر والاستعادة

يحوّل مسار GitLab CI أي commit إلى نسخة تعمل داخل حاوية بـ Gunicorn خلف Nginx، مع أدوات تهيئة أولية ونسخ احتياطي داخل شجرة المستودع.

  1. 1يُطلق أي دفع إلى GitLab مسار .gitlab-ci.yml
  2. 2يبني Dockerfile صورة التطبيق، وتسافر الأصول الثابتة المُجمّعة داخلها بفضل whitenoise
  3. 3يُشغّل docker-compose.yml التطبيق تحت gunicorn إلى جانب الوكيل العكسي nginx، مع بيئة تُزوَّد من ملف .env مبنيّ على نموذج .env.example
  4. 4يُهيّئ create_admin.py وsetup_categories.py أي نسخة جديدة لتصبح قابلة للاستخدام
  5. 5تلتقط الأدوات الموجودة في backups/ قاعدة البيانات والوسائط، بحيث يمكن استعادة أي نسخة بدل إعادة بنائها

أبرز الميزات

  • واجهة برمجة REST على Django
  • عميل جوال
  • التعامل مع الوسائط
  • نشر داخل حاويات
  • أدوات نسخ احتياطي

Outcomes

  • واجهة برمجة واحدة لعملاء متعدّدين
  • تسليم جاهز للحاويات

أعمال أخرى

CureAxis preview
منصّة رعاية صحية

CureAxis

منصّة رعاية صحية متعدّدة المستأجرين بمعايير HIPAA

عرض المشروع