1. مروری بر مکانیزم مهاجرت

1.1 مفهوم و نقش مهاجرت

مهاجرت پایگاه داده فرآیندی است که تغییرات مدل‌های داده را با ساختار پایگاه داده همگام می‌کند که برای ادامه داده‌ها حیاتی است. هر گاه نسخه برنامه به نسخه بروزرسانی یابد، مدل داده اغلب تغییراتی را تجربه می‌کند، مانند اضافه یا حذف فیلدها یا تغییر فهرست‌ها. مهاجرت به توسعه دهندگان امکان مدیریت این تغییرات را به صورت نسخه بندی شده و سیستماتیک فراهم می‌کند تا همخوانی بین ساختار پایگاه داده و مدل داده را تضمین کند.

در توسعه مدرن وب، مکانیزم مهاجرت از مزایای زیر برخوردار است:

  1. کنترل نسخه: فایل‌های مهاجرت تاریخچه تغییرات ساختار پایگاه داده را پیگیری کرده و این امر به ردگیری و درک تغییرات در هر نسخه راحتی می‌بخشد.
  2. پیاده‌سازی خودکار: از طریق مکانیزم مهاجرت، پیاده‌سازی و بروزرسانی پایگاه داده می‌تواند خودکار باشد و احتمال مداخله دستی و خطر خطا را کاهش دهد.
  3. همکاری تیمی: فایل‌های مهاجرت اطمینان می‌دهند که اعضای تیم از ساختارهای پایگاه داده همسان در محیط‌های توسعه مختلف استفاده می‌کنند که توسعه همکارانه را آسان‌تر می‌سازد.

1.2 ویژگی‌های مهاجرت چارچوب ent

ادغام چارچوب ent با مهاجرت ویژگی‌های زیر را فراهم می‌کند:

  1. برنامه‌نویسی اظهاری: توسعه دهندگان فقط باید بر روی نمایش Go موجودیت‌ها تمرکز کنند و چارچوب ent خواهد دستورالعمل تبدیل موجودیت‌ها به جداول پایگاه داده را اجرا می‌کند.
  2. مهاجرت خودکار: ent می‌تواند به صورت خودکار ساختار جداول پایگاه داده را ایجاد و به‌روزرسانی کند بدون نیاز به نوشتن دستورات DDL به صورت دستی.
  3. کنترل انعطاف‌پذیر: ent گزینه‌های پیکربندی مختلف را ارائه می‌دهد تا نیازمندی‌های مهاجرت متفاوت را پشتیبانی کند، مانند با یا بدون محدودیت‌های کلید خارجی و ایجاد شناسه‌های یکتا به صورت جهانی.

2. معرفی مهاجرت خودکار

2.1 اصول اساسی مهاجرت خودکار چارچوب ent

ویژگی مهاجرت خودکار از چارچوب ent بر اساس فایل‌های تعریف طرح (که به‌طور معمول در دایرکتوری ent/schema یافت می‌شوند) برای تولید ساختار پایگاه داده است. بعد از اینکه توسعه دهندگان موجودیت‌ها و روابط را تعریف کنند، ent ساختار موجود را در پایگاه داده بازرسی کرده و عملیات متناظر با ایجاد جداول، اضافه یا تغییر ستون‌ها، ایجاد فهرست‌ها و غیره تولید می‌کند.

علاوه بر این، اصل مهاجرت خودکار ent به صورت "حالت الحاقی" عمل می‌کند: به‌طور پیش‌فرض تنها جداول و فهرست‌های جدید یا ستون‌های جدید به جداول اضافه می‌شوند و جداول یا ستون‌های موجود حذف نمی‌شوند. این طراحی برای پیشگیری از از دست رفتن داده‌ها به طور تصادفی مفید است و به سادگی گسترش ساختار پایگاه داده را راحت می‌کند.

2.2 استفاده از مهاجرت خودکار

مراحل اساسی برای استفاده از مهاجرت خودکار ent به صورت زیر است:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("اتصال به MySQL ناموفق بود: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // انجام مهاجرت خودکار برای ایجاد یا به‌روزرسانی طرح پایگاه داده
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("ایجاد طرح پایگاه داده ناموفق بود: %v", err)
    }
}

در کد بالا، ent.Open مسئول برقراری اتصال با پایگاه داده و برگرداندن یک نمونه مشتری است، در حالی که client.Schema.Create عملیات مهاجرت خودکار واقعی را اجرا می‌کند.

3. استفاده‌های پیشرفته از مهاجرت خودکار

3.1 حذف ستون‌ها و فهرست‌ها

در برخی موارد، ممکن است نیاز باشد که ستون‌ها یا فهرست‌هایی که دیگر نیازمندی نیستند، از طرح پایگاه داده حذف شوند. در این زمان، می‌توان از گزینه‌های WithDropColumn و WithDropIndex استفاده کرد. به طور مثال:

// اجرای مهاجرت با گزینه‌های حذف ستون‌ها و فهرست‌ها.
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

این پارچه کد پیکربندی را برای حذف ستون‌ها و فهرست‌ها در هنگام مهاجرت خودکار فعال می‌کند. هنگام اجرای مهاجرت، ent هر ستون‌ها و فهرست‌هایی را که در تعریف طرح وجود ندارند را حذف می‌کند.

3.2 شناسه یکتا جهانی

به‌طور پیش‌فرض، کلیدهای اصلی در پایگاه داده‌های SQL از 1 برای هر جدول شروع می‌شوند و انواع موجودیت‌های مختلف ممکن است از یک شناسه یکسان استفاده کنند. در برخی سناریوهای برنامه، مانند استفاده از GraphQL، ممکن است ضروری باشد تا یکتایی جهانی برای شناسه‌های اشیاء انواع مختلف موجودیت فراهم شود. در ent، این می‌تواند از طریق گزینه WithGlobalUniqueID پیکربندی شود:

// اجرای مهاجرت با شناسه‌های یکتا جهانی برای هر موجودیت.
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("ایجاد طرح پایگاه داده ناموفق بود: %v", err)
}

با فعال‌سازی گزینه WithGlobalUniqueID، ent یک محدوده 2^32 برای هر موجودیت در یک جدول به نام ent_types اختصاص می‌دهد تا یکتایی جهانی را دست‌یابی کند.

حالت آفلاین

حالت آفلاین این امکان را به شما می‌دهد که تغییرات اسکیما را به جای اجرای مستقیم آنها در پایگاه داده، در یک io.Writer ذخیره کنید. این حالت برای تأیید دستورات SQL قبل از اعمال تغییرات یا برای تولید یک اسکریپت SQL برای اجرای دستی مفید است. به عنوان مثال:

// ذخیره تغییرات مهاجرت در یک فایل
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("خطا در ایجاد فایل مهاجرت: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("خطا در چاپ تغییرات اسکیمای پایگاه داده: %v", err)
}

این کد تغییرات مهاجرت را در یک فایل با نام migrate.sql ذخیره می‌کند. در عمل، توسعه‌دهندگان می‌توانند انتخاب کنند که مستقیماً روی خروجی استاندارد چاپ کنند یا برای بررسی یا ثبت رکورد در یک فایل ذخیره کنند.

4. پشتیبانی از کلیدهای خارجی و هوک‌های سفارشی

4.1 فعال یا غیرفعال کردن کلیدهای خارجی

در Ent، کلیدهای خارجی به وسیله تعریف روابط (یال‌ها) بین موجودیت‌ها اجرا می‌شوند، و این روابط کلید خارجی به صورت خودکار در سطح پایگاه داده ایجاد می‌شوند تا اطمینان از اصلاح و یکپارچه‌سازی داده فراهم شود. با این حال، در برخی موارد، مانند بهینه‌سازی عملکرد یا زمانی که پایگاه داده کلیدهای خارجی را پشتیبانی نکند، می‌توانید تصمیم بگیرید که آن‌ها را غیرفعال کنید.

برای فعال یا غیرفعال‌سازی محدودیت‌های کلید خارجی در مهاجرت‌ها، می‌توانید این کار را از طریق گزینه پیکربندی WithForeignKeys انجام دهید:

// فعال‌سازی کلیدهای خارجی
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("خطا در ایجاد منابع اسکیما با کلیدهای خارجی: %v", err)
}

// غیرفعال‌سازی کلیدهای خارجی
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("خطا در ایجاد منابع اسکیما بدون کلیدهای خارجی: %v", err)
}

این گزینه پیکربندی باید هنگام فراخوانی Schema.Create ارسال شود و تعیین می‌کند که آیا محدودیت‌های کلید خارجی را در DDL تولیدی بر اساس مقدار مشخص شده اضافه کند یا خیر.

4.2 اعمال هوک‌های مهاجرت

هوک‌های مهاجرت منطق سفارشی هستند که می‌توانند در مراحل مختلف اجرای مهاجرت درج و اجرا شوند. آنها برای انجام منطق خاص در پایگاه داده قبل/بعد از مهاجرت، مانند اعتبارسنجی نتایج مهاجرت و پرکردن پیشینه، بسیار کاربردی هستند.

اینجا یک مثال از چگونگی پیاده‌سازی هوک‌های سفارشی مهاجرت وجود دارد:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // کد سفارشی برای اجرا قبل از مهاجرت
        // به عنوان مثال، ثبت رویدادها، بررسی پیش‌شرط‌های خاص و غیره
        log.Println("منطق سفارشی قبل از مهاجرت")
        
        // فراخوانی هوک بعدی یا منطق مهاجرت پیش‌فرض
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // کد سفارشی برای اجرا بعد از مهاجرت
        // به عنوان مثال، پاک‌سازی، مهاجرت داده، بررسی‌های امنیتی و غیره
        log.Println("منطق سفارشی بعد از مهاجرت")
        return nil
    })
}

// استفاده از هوک‌های سفارشی در مهاجرت
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("خطا در اعمال هوک‌های سفارشی مهاجرت: %v", err)
}

هوک‌ها ابزارهای قدرتمند و بدست نیاوردنی برای مهاجرت‌های پیچیده هستند که به شما امکان کنترل مستقیم رفتار مهاجرت پایگاه داده را در مواقع لزوم می‌دهند.

5. مهاجرت‌های نسخه‌بندی شده

5.1 معرفی مهاجرت‌های نسخه‌بندی شده

مهاجرت نسخه‌بندی شده یک الگو برای مدیریت مهاجرت پایگاه داده است که به توسعه‌دهندگان امکان می‌دهد تا تغییرات ساختار پایگاه داده را به چندین نسخه تقسیم کرده و هر کدام شامل یک مجموعه خاص از دستورات تغییرات پایگاه داده باشند. در مقایسه با مهاجرت خودکار، مهاجرت نسخه‌بندی شده کنترل دقیق‌تری را فراهم می‌کند و امکان پیگیری و واگرایی تغییرات ساختار پایگاه داده را تضمین می‌کند.

مزیت اصلی مهاجرت نسخه‌بندی شده، پشتیبانی از مهاجرت به سمت جلو و عقب (یعنی ارتقا یا برگشت) است که به توسعه‌دهندگان امکان می‌دهد تا تغییرات خاص را اعمال، بازگشت دهند یا آن‌ها را نادیده بگیرند. هنگام همکاری در یک تیم، مهاجرت نسخه‌بندی شده اطمینان می‌دهد که هر عضو بر روی ساختار پایگاه داده یکسان کار می‌کند و مسائل ناشی از عدم تطابق را کاهش می‌دهد.

مهاجرت خودکار اغلب غیرقابل برگشت است، دستورات SQL را برای تطابق با آخرین وضعیت مدل‌های موجودیتی تولید و اجرا می‌کند و اصولاً در مراحل توسعه یا پروژه‌های کوچک استفاده می‌شود.

5.2 استفاده از مهاجرت‌های نسخه‌بندی شده

1. نصب ابزار Atlas

قبل از استفاده از مهاجرت‌های نسخه‌بندی شده، شما باید ابزار Atlas را در سیستم خود نصب کنید. Atlas یک ابزار مهاجرت است که از چندین سیستم پایگاه داده پشتیبانی می‌کند و قابلیت‌های قدرتمندی برای مدیریت تغییرات طرح پایگاه داده ارائه می‌دهد.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. تولید فایل‌های مهاجرت بر اساس تعریف فعلی موجودیت‌ها

atlas migrate diff migration_name \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. فایل‌های مهاجرت برنامه

با تولید فایل‌های مهاجرت، می‌توان آن‌ها را به محیط‌های توسعه، آزمون یا تولید اعمال کرد. معمولاً ابتدا این فایل‌های مهاجرت را به پایگاه داده توسعه یا آزمون اعمال می‌کنید تا اطمینان حاصل شود که به درستی اجرا می‌شوند. سپس، مراحل مهاجرت مشابه در محیط تولید اجرا می‌شود.

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

از دستور atlas migrate apply استفاده کنید و دایرکتوری فایل‌های مهاجرت (--dir) و آدرس اینترنتی پایگاه داده مقصد (--url) را جهت اعمال فایل‌های مهاجرت مشخص کنید.