ما هي الـ Migration وما علاقتها بالـ Database ؟

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 10 دقائق

المقدمة

اليوم سنتحدث عن مفهوم الـ Migration في الـ Database وكيفية استخدامها بشكل صحيح
سأكتفي بشرح الفكرة العامة للـ Migration وكيفية استخدامها ولن أدخل في تفاصيل تقنية كثيرة
لأن الهدف من هذا المقال هو توضيح الفكرة العامة للـ Migration وفائدتها وكيفية استخدامها لا أكثر

تخيل معي أنك تعمل وحدك في تطوير مشروعك الخاص وقمت ببناء قاعدة بيانات
ومع مرور الوقت أثناء تطويرك للمشروع تقوم بإضافة أو تعديل أو حذف حقول أو عناصر جديدة في الجداول أو تقوم بحذف جداول بأكملها
أو إضافة علاقات جديدة بين الجداول وهكذا

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

لكن ماذا لو كان لديك فريق عمل كبير وكل عضو في الفريق يقوم بتعديلات على قاعدة البيانات الخاصة بالمشروع
كيف سيتم تتبع هذه التعديلات ؟ وكيف ستقومون بتنسيق هذه التعديلات بينكم ؟

بمعنى لنفترض أنك تعمل في مشروع مع بلال مشروع صغير وبسيط وقمت أنت بإنشاء قاعدة بيانات لهذا المشروع
الآن أنت قمت بتشغيل المشروع على جهازك وقام بلال بتشغيل المشروع على جهازه
وكل شخص بالطبع قام بإنشاء Database خاصة به على جهازه كـ LocalHost

لنفترض أنك كنت المسؤول عن اضافة ميزة جديدة وهذه الميزة تتطلب إضافة حقل جديد لجدول معين
لنفترض أنك أضفت حقل يسمى phone لجدول users
وبعد أن قمت بإضافته واختبرت الميزة وعملت بشكل جيد قمت برفع التعديلات على الـ Repository الخاص بالمشروع
الآن سيقوم بلال بتنزيل التعديلات الجديدة من الـ Repository وسيقوم بتشغيل المشروع على جهازه الخاص

لكن سؤال كيف سيعرف بلال أنه يجب عليه إضافة حقل phone لجدول users ؟
هل تقوم أنت بإرسال له رسالة تقول له "مرحبًا بلال آسف على الإزعاج لكن تذكر أن تضيف phone لجدول users"
ولنقل أنك أخبرته، لكن هل سيتذكر ذلك ؟ وهل سيقوم بإضافته بشكل صحيح من الأساس ؟
بمعنى حتى لو أخبرته كيف يمكنك التأكد من أنه قام بإضافته بشكل صحيح ؟
هل ستقوم بارسال ملف SQL له يحتوي على الأوامر اللازمة لإضافة الحقل وتقول له "قم بتشغيل هذا الملف رجاءًا قبل أن تفعل أي شيء آخر"

قد تظن أن اضافة بسيطة مثل اضافة phone لجدول users ليست مشكلة ولكن ماذا لو كانت هناك تعديلات كبيرة وكثيرة ؟
لنقل أنك قمت بعمل جدول جديد وضخم وأضفت Relation بينه وبين جدول آخر أو Polymorphic Relation
هل ستقوم بإرسال له ملف SQL يحتوي على الأوامر اللازمة لإنشاء الجدول والعلاقات ؟

وطبعًا لن يكون هناك بلال بل سيكون هناك عبدالله وأشرف وفريق كامل يعمل على المشروع هل ستقوم بفتح اذاعة داخل الشركة تخبرهم وكل شخص يخبر الآخر

وإليك حوار تخيلي قد يحدث بينكم:

ويمكنك أن تتخيل الفوضى التي ستحدث والأخطاء والتعارض بين البيانات والتأخير

هنا يأتي دور الـ Database Migration لحل هذه المشكلة وتنظيم عملية التعديل على قاعدة البيانات بشكل صحيح ومنظم

لكن سؤال قبل أن نكمل، هل هذا يعني أنني أستعمل الـ Migration فقط داخل فريق كبير ؟ الإجابة لا، الـ Migration مفيد حتى وإن كنت تعمل وحدك على مشروع صغير وبسيط
الفكرة أنه يكون مكان واحد يكون فيه كل الجداول والتعديلات التي تمت على الـ Database والتي يحتاجها أي شخص ليبدأ بالعمل على المشروع
ويستطيع إنشاء قاعدة بيانات جديدة والتعديلات الجديدة بسهولة

وأيضًا وقد يكون أهم شيء وهو تطبيق التعديلات على الـ Server نفسه الذي ترفع عليه المشروع
بمعنى وإن كنت تعمل وحدك على المشروع وتستخدم LocalHost لتشغيل المشروع وتعمل على تعديلات على قاعدة البيانات
ستضطر لاحقًا لرفعه على الـ Server كيف ستقوم بتطبيق التعديلات على الـ Server ؟
هل في كل مرة ستقوم بالدخول لداخل الـ Server في الـ Terminal وتقوم بتنفيذ أوامر الـ SQL والتعديلات يدويًا ؟

هنا أيضًا نستفيد من الـ Migration واستخدامها في الـ CI/CD والـ Deployment لتطبيق التعديلات بشكل تلقائي عند رفع المشروع على الـ Server

حسنًا أعتذر، أعرف أنني تكلمت بشكل نظري عن الـ Migration ولم أشرح ما هو الـ Migration بالضبط وكيفية استخدامها
الآن سأقوم بشرح الـ Migration بالتفصيل وكيفية استخدامها

ما هي الـ Migration ؟

الـ Migration بكل بساطة هي مجموعة من الملفات التي تستخدم لبناء وتعديل قاعدة البيانات
فعلى سبيل المثال لنقول أنك بدأت مشروع جديد مع فريقك وأنت كنت المسؤول عن بناء قاعدة البيانات المبدئية
فأنت الآن قمت بإنشاء جدول يدعى Users ليمثل المستخدمين ويحتوي على id, name, email, password

عندما تنشيء شيء جديد أو تعديل جديد على الـ Database ستقوم بإنشاء ملف يحتوى على التعديلات وتضعه على سبيل المثال في مجلد يدعى migrations
هذه الملف ما رأيك بأن نسميه باسم معبر مثل 2024_10_01_123000_create_users_table.php
حيث يحتوى على تاريخ الانشاء ثم الوصف لتوضح متى تمت الاضافة او التعديل وماذا يفعل هذا الملف بالتحديد
ونحن فقط قمنا بإضافة جدول جديد لا أكثر

وبالمناسبة التاريخ غالبًا ما يكون بصيغة YYYY_MM_DD_HHMMSS أي سنة_شهر_يوم_ساعة_دقيقة_ثانية
بالتالي فالتاريخ هنا هو 2024_10_01_123000 وهو يعني أن الملف تم إنشاؤه في الأول من أكتوبر 2024 الساعة 12:30:00

ما محتوى وشكل الملف بالتحديد ؟

الملف يحتوى على دالتين أساسيتين وهما up و down
حيث أن up تحتوي على الأوامر التي تقوم بإنشاء الجدول أو تعديله
و down تحتوي على الأوامر التي تقوم بالتراجع عن التغيرات التي في up

في حالة الجدول الذي أنشأناه Users سيكون الملف كالتالي:

class CreateUsersTable
{
    public function up()
    {
      DB::raw('CREATE TABLE users (
          id INT PRIMARY KEY AUTO_INCREMENT,
          name VARCHAR(255),
          email VARCHAR(255),
          password VARCHAR(255)
        )');
    }

    public function down()
    {
      DB::raw('DROP TABLE users');
    }
}

لاحظ هنا أن في دالة up نقوم بإنشاء الجدول users بالطريقة التي نريدها
وفي دالة down نقوم بحذف الجدول users

ملحوظة: لاحظ أنني كتبت أوامر الـ SQL بشكل مباشر أي raw لكن غالبًا ويفضل أن تعتمد على المكاتب والـ ORM أو ODM ليهتم بأمر الـ SQL بداخلها بشكل أفضل وأكثر أمانًا

فعلى سبيل المثال في لغة مثل PHP ستكون هناك مكتبات تسهل عليك عملية الـ Migration
ففي الـ Laravel على سبيل المثال ستقوم بكتابة الملف كهذا

class CreateUsersTable
{
    public function up()
    {
        Schema::create('users', function ($table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('password');
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}

وكذلك مع جميع اللغات والـ Frameworks ستجد مكتبات تسهل عليك عملية الـ Migration
وتعتمد على نفس المبدأ حيث أن هناك دالة up بها الأوامر التي نريد تنفيذها ودالة down بها الأوامر التي تقوم بالتراجع عن التغييرات

الآن أصبح لدينا ملف يحتوي على التعديلات والإضافات التي قمنا بها في الـ Database
الآن ما الخطوة التالية ؟
الخطوة التالية هي تنفيذ هذا الملف على الـ Database

كيفية تنفيذ الـ Migration ؟

بكل بساطة بعد أن قمت بكتابة الملف ووضعته في مجلد الـ migrations يمكنك تنفيذه بسهولة
وغالبًا الـ Framework الذي تستخدمه يوفر لك أوامر لتنفيذ الـ Migration بسهولة
وإن كنت لا تستخدم Framework يمكنك أن تمر على ملفات الـ Migration وتنفذ دالة up بكل بساطة

في الـ Laravel يمكنك تنفيذ الـ Migration بكل بساطة عن طريق الأمر

php artisan migrate

هكذا سيتم المرور على ملفات الـ Migration وتنفيذها على الـ Database بشكل مباشر
بالتالي سيتم إنشاء جدول users التي أنشأناه داخل ملف الـ Migration وسيتم تنفيذ الأوامر التي في دالة up

وفي حالة أردت التراجع عن التعديلات يمكنك استخدام الأمر

php artisan migrate:rollback

هكذا سيتم التراجع عن آخر تعديل تم على الـ Database وسيتم تنفيذ الأوامر التي في دالة down
لكن هناك ملحوظة مهمة عن موضوع التراجع عن التعديلات سأتحدث عنها لاحقًا في الفقرة القادمة

ملحوظة: أنا أستخدم Laravel لتوضيح لا أكثر وكل شيء يمكنك تنفيذه في أي Framework أو بدون Framework بنفس الطريقة
طالمًا فهمت الفكرة العامة

الآن نحن قمنا بإنشاء جدول users ونفذناه على الـ Database
واختبرناه وكل شيء سار بشكل جيد

الآن غالبًا ستقوم برفع تعديلات وملف الـ Migration على الـ Repository الخاص بالمشروع
ثم يأتي أعضاء فريق كل واحد على جهازه الخاص سيقوم فقط بتنفيذ الـ Migration على الـ Database الخاص به
وهكذا سيتم تطبيق التعديلات على الـ Database بشكل سليم ومنظم وبدون أخطاء كما كتبتها أنت في ملف الـ Migration

تنفيذ الـ Migration مع الفريق

الآن لنفترض أن كل واحد في الفريق يقوم بتنفيذ شيء معين في المشروع

فمثًلا :

كل شخص يقوم بتعديلاته في جهازه بشكل مستقل ويكتب أي تغيرات يقوم بها على الـ Database في ملف داخل مجلد الـ migrations

فغالبًا أنت انتهيت من تعديلك وأنشأت ملف يدعى 2024_10_02_123000_add_phone_to_users_table.php وقمت برفعه على الـ Repository
ثم عبدالله انتهى وأنشأ ملف يدعى 2024_10_03_123000_create_posts_table.php وقام برفعه على الـ Repository
وبلال قام بإنشاء ملف يدعى 2024_10_04_123000_create_permissions_table.php و 2024_10_04_135000_create_user_permissions_table.php وقام أيضًا برفعهما
وأشرف حسنًا قام .. ااا .. بعمل ملف يدعى 2024_10_05_123000_add_something_to_something_table.php وقام برفعه

الآن كل شخص رفع تعديلاته وكل شخص قام بتنزيل الملفات التي أنشأها الآخرين من الـ Repository
والآن مجلد الـ migrations الخاص بكل شخص سيكون بهذا الشكل

migrations
├── 2024_10_01_123000_create_users_table.php
├── 2024_10_02_123000_add_phone_to_users_table.php
├── 2024_10_03_123000_create_posts_table.php
├── 2024_10_04_123000_create_permissions_table.php
├── 2024_10_04_135000_create_user_permissions_table.php
└── 2024_10_05_123000_add_something_to_something_table.php

الآن كل شخص سيقوم فقط بتنفيذ الـ Migration لتتطبق كل هذه التغيرات والإضافات على الـ Database
وبهذه الطريقة يمكنك تنظيم عملية التعديل على الـ Database بشكل جيد ومنظم وتجنب النسيان أو الأخطاء
ولا مزيد من يا بلال ابعت الملف أو يا عبدالله هل نفذت التعديلات التي ارسلتها لك منذ يومين وهكذا

وأيضًا غالبًا عندما تقوم برفع هذه التعديلات على الـ Server تستطيع جعله تلقائيًا ينفذ الـ Migration عند رفع أي تعديل

بعض الملحوظات التي عليك الانتباه لها

أثناء استخدام الـ Migration هناك بعض الأمور التي يجب عليك الانتباه لها
أي ملف أنشأته ورفعته على الـ Repository لا تقم بتعديله مرة أخرى
لأن تم تنفيذه ولن يتم تنفيذ مرة أخرى

├── 2024_10_01_123000_create_users_table.php
├── 2024_10_02_123000_add_phone_to_users_table.php

فعلى سبيل المثال لاحظ أننا قمنا بإنشاء جدول users وأضفنا phone له
ولكن لما لم نقم بتعديل ملف 2024_10_01_123000_create_users_table.php وإضافة phone للجدول
السبب لأن الملف تم رفعه و تم تنفيذه عند باقي أعضاء الفريق والأهم من هذه أنه تم تنفيذه على الـ Server

بالتالي المشكلة أنك إذا قمت بتعديل الملف وأضفت phone للجدول ورفعته على الـ Repository
وطالما أنه تم تنفيذه من قبل فلن يتم تنفيذه مرة أخرى ولن يتم إضافة phone للجدول users
لأن الـ Framework أو الـ Migration سيقوم بالتحقق من الملفات التي تم تنفيذها ولن يقوم بتنفيذ أي ملف تم تنفيذه من قبل
وهذا يعتبر من الأمور الجيدة لأنه يمنع تنفيذ نفس التعديلات مرتين

وأيضًا لو افترضنا أنك قمت بتعديل الملف وأضفت phone للجدول ورفعته على الـ Repository
ستحتاج لعمل rollback للتراجع عن التعديلات التي قمت بها في الـ Migration السابقة والتراجع عنها في الـ Server وعند كل أعضاء الفريق بشكل يدوي
وهذه العملية تعتبر كارثة لأنك غالبًا ستفقد البيانات المتواجدة في الـ Database لأن في حالتنا هذه التراجع يعني حذف الجدول users بأكمله وإعادة إنشائه من جديد بالتعديلات الجديدة
بالتالي البيانات التي به سيتم فقدها

لذا يجب عليك الانتباه لهذه النقطة وعدم تعديل الملفات التي تم تنفيذها من قبل
اعتبر أن الملفات التي تم تنفيذها أصبحت من المضي وأصبح كتاريخ للتعديلات التي تمت على الـ Database على مر الزمن
وأي تعديل جديد يجب أن يكون في ملف جديد

لذا فكما رأيت هنا :

├── 2024_10_01_123000_create_users_table.php
├── 2024_10_02_123000_add_phone_to_users_table.php

لو أردت أن تضيف phone للجدول users مرة أخرى يجب عليك إنشاء ملف جديد يحتوي على التعديلات الجديدة
كما فعلنا في الملف 2024_10_02_123000_add_phone_to_users_table.php ولا نقم أبدًا بتعديل ملف تم رفعه وتم تنفيذه من قبل عند الآخرين وفي الـ Server

لذا طالما رفعت ملفًا وتم تنفيذه فلا تقم بعمل rollback ولا تقم بتعديله مرة أخرى
فكرة دالة down والـ rollback هي للتراجع عن التعديلات التي تقوم باختبارها على جهازك الخاص وليس للتعديل على التعديلات التي تم تنفيذها من قبل
فمثلًا أنت قمت بإنشاء جدول users وأضفت phone ثم قمت بعمل migration وتم تنفيذه على الـ Database الخاصة بك فقط
ثم قررت أن تغير phone إلى mobile تستطيع عمل rollback وتنفيذ الـ Migration الجديدة وتجربتها على جهازك الخاص
لأنك في هذه الحالة تختبرها على جهازك الخاص ولم تقم برفعها بعد على الـ Repository ولم يتم تنفيذها على الـ Server
بالتالي يمكنك فعل ما تشاء وتجربة التعديلات الجديدة على الـ Database الخاصة بك كما تشاء قبل أن ترفعها

الختام

هذا هو شرح بسيط للـ Migration وكيفية استخدامها وفائدتها وكيفية تنظيم عملية التعديل على الـ Database بشكل جيد
المقالة قصيرة لأنني لم أرد أن أطيل عليكم مثل كل مرة لذا يكفي أننا فهمنا الفكرة العامة للـ Migration وكيفية استخدامها

وفهمنا فائدتها وكيفية تنظيم عملية التعديل على الـ Database بشكل جيد ومنظم وكيفية تطبيق التعديلات على الـ Server بشكل تلقائي
وعند الآخرين بكل بساطة