6  متابعة تطورات المخطط

السؤال: ما هي الطريقة السليمة لتطوير مخطط قاعدة البيانات بحيث نتجنَّب خسارة البيانات أو تعارض المخطط التجريبي مع المخطط الحقيقي؟

للإجابة عن هذا يجب علينا معرفة خطورة التغييرات غير المكتوبة! خصوصًا حين يتشعب البرنامج وتكثر التفاصيل ونحتاج إلى ضبط عمليَّة التطوير لتتم بشكل احترافي خاضع لمعايير هندسية في الجودة.

الإشكال يكمُن في أن التغيير أمر لا مفر منه. فكلمة البرمجيَّة (Software) ترمُز كلمة (Soft) فيها لليونتها بعكس العتاد (Hardware) الذي هو معدَنٌ محفور. فإن من طبيعة البرمجيات النسخ المتتابعة الكثيرة التي لابد أن تتعارض في أجزائها مع بعض أجزائها الأخرى.

برمجيات التحكم في نُسَخ النص البرمجي: git مثالاً

اعتمد المبرمجون أدوات لمتابعة التغير في النص البرمجي نفسه عبر الزمن. تسمى هذه الأدات: نظام التحكم النسخ (Version Control System - VCS) ومن أشهر أمثلته برمجيَّة git التي طوَّرها المهندس الفنلندي (لينس تورفلادز - Linus Torvalds) أثناء اختراعه لنظام لينكس (Linux) ليسهِّل متابعة تطوُّر الكود.

إحدى خصائص قت المشهورة: إظهار الفرق (diff) بين نقطتي حفظ لنفس الملف:

صورة: على اليمين النص الجديد (الأخضر إضافات)، على اليسار النص القديم (الأحمر حذف).

ومن خصائص git:

  1. متابعة تطوُّر ملفات المشروع بحسب نقاط الحفظ المعتمدة والمسندة لمؤلفيها سطرًا بسطر، بالوقت والتاريخ، وسبب التعديل.
  2. إمكان الرجوع بالزمن لنقطة حفظ معيَّنة لسطر معيَّن؛ هذا يعني (Undo / Ctrl+Z) لأعلى مستوى ممكن
  3. إضافة نقطة تحقق قبل اعتماد التغيير من المساهم إلى النسخة الأخيرة؛ تسمح بمراجعة كل تغيير من كل مساهِم طِبقًا لمعايير المشروع (الصحة، الجودة، الأمن، الكفاءة، …إلخ).

إن استعمال git من المهارات التي إذا فقدها المبرمج؛ فإننا نتساءل في كوْنه مبرمجًا حقيقيًا أو يدعي ذلك فقط. قد تجد هذا الموقع مفيدًا في تعلم أهم ما في git في أقل وقت ممكن. الرابط: https://git30minutes.com/

أو هذا الكتاب المجاني في الوقع الرسمي لتحميل برمجية git نفسها: https://git-scm.com/learn

ضرورة متابعة تطورات المخطط

أوامِر التعريف (DDL) من إنشاء وتعديل وحذف على مخطط البيانات (Data Schema) إذا تم تنفيذها على جهاز أحد المطورين، فإنه لا ضمان عندنا للتكرارية؛ أي: لتناظر المخطط عند كل المطورين للبرنامج. يحصل هذا حين تكون الأوامِر مُرسلَة غير مدوَّنة ولا مقيَّدة؛ فهي تُكتَب في سطر الأوامر وينفذها نظام قاعدة البيانات.

الإشكال الثاني هو أن هذه الأوامِر تعديلٌ يصعب الرجوع عنه. فنحن إذا طبقنا هذا التغيير في بيئة الإنتاج / التشغيل (عند المستخدم الحقيقي)، ثم تبيَّن لنا أنه أدى لخطأ، فكيف نعيد الحال السابقة للتغيير! سنتأخر كثيرًا إن لم يُضبَط الأمر وتتم أتمتته.

وحلُّ هذه المشكلة: أن يتم كتابة الأوامِر هذه نصًّا برمجيًّا وتُدرَجُ ضمن ما يُتابَع في نظام التجكم بالنسخ (git)، ويكون هو المرجِع فيما تمَّ إجراؤه أو لم يتمَّ من أوامِر التعريف على مخطط البيانات.

وفوق ذلك فإن كل عمليَّة إضافية يتم معها كتابة عكسها، حتى يتسنى لنا الرجوع عنها بسرعة.

وبهذا يكون لدينا تطوُّر المخطط عبر الزمن مدوَّنًا، وقابلًا للتنفيذ بشكل تلقائي في أي بيئة. ويُمكن أن نخطوَ بعض الخطو فيه ثم نرجع عددًا ما من الخطوات.

منح المبرمجون اسمًا لهذه الآلية، وهي نصوص التغيير (Change Scripts)؛ فهي تتابع تطورات المخطط تعديلاً تعديلاً. والمصطلح الشائع له في قواعد البيانات: نصوص الترحيل (Migration Scripts).

إنشاء نصوص التغيير

أداة dbmate؛ هي أداة لترحيل قواعد البيانات تحافظ على تزامن مخطط قاعدة بياناتك بين المطورين المتعددين وخوادم الإنتاج لديك.

التثبيت

راجع طريقة التثبيت في الصفحة.

على نظام لينكس:

sudo curl -fsSL -o /usr/local/bin/dbmate https://github.com/amacneil/dbmate/releases/latest/download/dbmate-linux-amd64
sudo chmod +x /usr/local/bin/dbmate
/usr/local/bin/dbmate --help

الأوامر

dbmate --help    # print usage help
dbmate new       # generate a new migration file
dbmate up        # create the database (if it does not already exist) and run any pending migrations
dbmate create    # create the database
dbmate drop      # drop the database
dbmate migrate   # run any pending migrations
dbmate rollback  # roll back the most recent migration
dbmate down      # alias for rollback
dbmate status    # show the status of all migrations (supports --exit-code and --quiet)
dbmate dump      # write the database schema.sql file
dbmate load      # load schema.sql file to the database
dbmate wait      # wait for the database server to become available

يبحث البرنامج عن متغيِّر بيئة باسم DATABASE_URL، كما أنه يقرأ الملف الذي باسم .env إن وجده بالجوار. سننشئ هذا الرابط على النحو المتوقع، وهو:

protocol://username:password@host:port/database_name?options

نكتب في ملف .env الآتي:

DATABASE_URL="postgres://user123:password123@127.0.0.1:5432/testdb?sslmode=disable"

لاحظ وجود ?sslmode=disable وذلك أنَّنا نريد تعطيل هذه الخاصيَّة الأمنية لأننا لم نقم بتفعيلها أصلاً.

إنشاء نصوص التغيير

ننفذ الأمر:

dbmate new "add_users_table"

سينشئ هذا ملفًّا فيه قسمان على هذا النحو:

-- migrate:up

-- migrate:down

نضع تحت القسم الأوَّل التعديل الجديد، وعكسه تحت القسم الثاني. وهذا مثال:

-- migrate:up
CREATE TABLE users (id SERIAL PRIMARY KEY);

-- migrate:down
DROP TABLE users;

لاحظ أن أمر إنشاء الجدول CREATE TABLE يعكسه أمر حذف الجدول DROP TABLE؛ وكلاهما يعمل على نفس الجدوَل.

ثم نحدِّث نماذجنا في بايثون لتعكس التغيير الجديد:

from sqlalchemy import MetaData, Table, Column, Integer

metadata = MetaData()

users = Table(
    "users",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
)

إجراء نصوص التغيير

ننفذ الأمر:

dbmate up

وإن أردنا العكس استعمال النقيض:

dbmate down

كما يمكنك معرفة حال الترحيل بالأمر:

dbmate status

المرة الثانية: تغييرات جديدة

ومثل ذلك سيكون في تغيير الأعمدة. تأمل هذا المثال:

-- migrate:up
ALTER TABLE users ADD COLUMN email VARCHAR;

-- migrate:down
ALTER TABLE users DROP COLUMN email;

هناك خياران في إضافة التغييرات في كل مرة:

  1. يمكنك تكرار هذه الأقسام لتكون التغييرات في ملف واحد
  2. أو أن تنشئ ملفًا جديدًا لكل تغيير على حدة (dbmate new)

ثم نعكس هذا التحديث الجديد في بايثون، ليصبح الجدول الآن بهذا الشكل:

users = Table(
    "users",
    metadata,
    Column("id", Integer, primary_key=True, autoincrement=True),
    Column("email", String), # <== NEW
)

وحتى لا يفوتنا أن نعدل هذا ونترك ذك، نؤكد على جعل التغيير في الملفين (نص التغيير وبايثون) ضِمن تعديل واحد في سجل git. أي: يتم إضافتهما:

git add db/migrations
git add src/models.py

ثم اعتمادهما برسالة تلخِّص التغيير الذي حصل:

git commit -m "schema: add email column to users table"

خلاصة

المشكلة ليست في تغيير مخطط قاعدة البيانات؛ المشكلة في تغييره سرًّا. أي تعديل غير مدوَّن في النص البرمجي فإنه نقطة ضعف في تطوُّر البرمجيَّة: مطوّر يضيف عمودًا، آخر لا يعلم بوجوده، والبيئة الإنتاجية تصبح في خطر.

الحلّ: التعامل مع مخطط البيانات كما نتعامل مع الكود—بنظام نسخ واضح، قابل للرجوع، وتحت رقابة git. هنا يأتي دوْر آلية ضبط تطوُّر المخطط: ملفات صغيرة تروي قصة كل تغيير بدقة الجرّاح، ومعها ترياق الرجوع عند الخطأ.

أداة dbmate تؤتمت العملية: توليد ملف ترحيل، كتابة التعديل في قسم migrate:up، وكتابة مضادّه في migrate:down. بعدها تُنفَّذ بـ dbmate up، وتُلغى بـ dbmate down .. آلية منظَّمة بدون خسارة بيانات.

أخيرًا: كل تغيير في المخطط يجب أن ينعكس على النموذج في بايثون، ويُعتمد الاثنان في نفس عملية git commit. وبهذا يصبح المخطط محفوظًا، متزامنًا، ويمكن الرجوع في الزمن.

بهذا نكون ختمنا المحور الأساسي الذي لا يستغني عنه أي تطبيق: قاعدة البيانات.