Drift (Moor) — این چیست، ORM واکنشگرا و کار با پایگاه‌های داده

نویسنده: IT Sectr منتشر شده: 2026-03-13 زمان مطالعه: 9 دقیقه

Drift (پیشتر Moor) — ORM واکنشگرا برای Flutter و Dart ساخته شده بر روی SQLite با DSL مخصوص خود برای کوئری‌ها. برخلاف ORM‌های سنتی، Drift کوئری‌های Dart را در مرحله build به SQL کامپایل می‌کند و خطاهای runtime را حذف می‌کند. به گفته Drift Docs, 2024، Drift تا 40% کد بیشتر نسبت به کوئری‌های دستی SQL تولید می‌کند، اما نوشتن دستی SQL را کاملاً حذف کرده و آن را با نحو type-safe Dart جایگزین می‌کند.

نکات اصلی

  • Drift — ORM واکنشگرا با کامپایل کوئری‌ها به SQL در مرحله build
  • کوئری‌های DSL — رابط fluent در Dart بدون نوشتن رشته‌های SQL
  • واکنشگرایی — Stream و کوئری‌های auto-updating برای به‌روزرسانی UI در زمان واقعی
  • چندسکویی — Android, iOS, Web, macOS, Linux, Windows
  • مهاجرت‌ها — نسخه‌بندی خودکار و مهاجرت‌های دستی از طریق SQL

Drift چیست؟

Drift — ORM برای Dart و Flutter که پیشتر با نام Moor شناخته می‌شد. توسط Simon Binder در سال 2019 توسعه یافت و از آن زمان چندین نسخه اصلی را پشت سر گذاشته است. Drift کوئری‌های Dart را با استفاده از drift_dev و build_runner در مرحله build به SQL کامپایل می‌کند که امنیت نوع کامل را فراهم کرده و خطاهای نحوی SQL را در runtime حذف می‌کند.

برخلاف Floor، Drift از DSL (Domain-Specific Language) مخصوص خود برای ساخت کوئری‌ها استفاده می‌کند — توسعه‌دهنده به Dart می‌نویسد و مولد آن را به SQL ترجمه می‌کند. این به IDE امکان بررسی نحو، تکمیل خودکار فیلدهای جدول و بازسازی مدل داده را بدون ترس از شکستن کوئری می‌دهد.

طبق Drift (2024)، این کتابخانه در بیش از 8000 پروژه Flutter استفاده می‌شود. از تمامی پلتفرم‌های محبوب پشتیبانی می‌کند: Android از طریق sqflite، iOS از طریق sqflite، وب از طریق sqlite3 WASM، دسکتاپ از طریق درایور بومی sqlite3.

تاریخچه تغییر نام: Moor به Drift

Moor در نسخه 2.0 (2022) به Drift تغییر نام داد. دلیل — تداخل نام با پروژه‌های دیگر و تمایل به فاصله گرفتن از کد قدیمی. API سازگار باقی ماند: برای مهاجرت کافیست import را از moor به drift تغییر داده و وابستگی‌ها را به‌روز کنید.

قابلیت‌های کلیدی

Drift فراهم می‌کند: کوئری‌های Stream داخلی با به‌روزرسانی خودکار هنگام تغییر داده، پشتیبانی از تراکنش‌ها با بازگشت، کوئری‌های سفارشی SQL از طریق rawQuery، الگوی DAO برای کپسوله‌سازی منطق، مهاجرت‌های چندسکویی و یکپارچه‌سازی با Riverpod و BLoC از طریق بسته‌های drift_riverpod و drift_bloc.

Drift چگونه کار می‌کند؟

Drift از تولید کد در مرحله کامپایل استفاده می‌کند. توسعه‌دهنده جداول را از طریق حاشیه‌نویسی @DataClass یا کلاس‌های Dart که Table را گسترش می‌دهند توصیف می‌کند. مولد کلاس‌های کمکی ایجاد می‌کند: Companion (برای فیلدهای nullable در درج/به‌روزرسانی)، DriftDatabase (نقطه ورود) و پیاده‌سازی‌های DAO.

کوئری‌های SQLite را Drift مستقیماً اجرا نمی‌کند. در عوض توسعه‌دهنده به Dart می‌نویسد: select(tasks).where(tasks.priority.greaterThan(3)).build(). مولد این را به SQL ترجمه می‌کند و در زمان اجرا Drift به سادگی کوئری آماده SQL را به SQLite ارسال می‌کند. این راحتی نحو Dart را با کارایی SQL بومی ترکیب می‌کند.

معماری کوئری‌ها

Drift از دو حالت کوئری پشتیبانی می‌کند: DSL (توصیه شده) و raw SQL. کوئری‌های DSL ایمن‌تر هستند — کامپایلر نام فیلدها، نوع‌ها و سازگاری را بررسی می‌کند. Raw SQL برای کوئری‌های پیچیده‌ای که توسط DSL پوشش داده نمی‌شوند مورد نیاز است: توابع پنجره‌ای، CTE بازگشتی، افزونه‌های خاص SQLite.

Drift DSL در مقابل SQL: مقایسه رویکردها

Drift دو روش برای نوشتن کوئری ارائه می‌دهد: Dart DSL (بومی) و raw SQL (برای موارد پیچیده). DSL برای 90% سناریوها ترجیح داده می‌شود: ایمن‌تر، خواناتر و از بازسازی پشتیبانی می‌کند. Raw SQL فقط زمانی استفاده می‌شود که DSL ساختار مورد نیاز را پوشش ندهد.

جنبهDrift DSLRaw SQL در Drift
امنیت نوعکامل (کامپایل)ندارد (runtime)
تکمیل خودکاربله (IDE)فقط در فایل‌های sql
بازسازیخودکارجستجوی دستی در رشته‌ها
JOIN پیچیدهپشتیبانی می‌شودآزادی کامل
توابع پنجره‌ایمحدودپشتیبانی کامل
واکنشگراییداخلی (Stream)از طریق .watch()

چه زمانی از DSL استفاده کنیم

Drift DSL — روش اصلی کار. SELECT، INSERT، UPDATE، DELETE، WHERE، ORDER BY، LIMIT، JOIN و گروه‌بندی را پوشش می‌دهد. برای تمام کوئری‌های معمول CRUD از DSL استفاده کنید: کوتاه‌تر، ایمن‌تر و Stream را هنگام تغییرات به طور خودکار به‌روز می‌کند.

چه زمانی از raw SQL استفاده کنیم

Raw SQL در Drift برای موارد زیر لازم است: توابع سفارشی SQLite (FTS5, JSON1)، زیرکوئری‌های پیچیده با EXISTS، INSERT OR REPLACE، به‌روزرسانی انبوه با CASE، همچنین کوئری‌هایی که در آنها کارایی بحرانی است و DSL برنامه اجرای بهینه تولید نمی‌کند. Raw SQL را می‌توان در فایل‌های .sql با پشتیبانی از تایپ‌سازی از طریق drift_dev نوشت.

مثال‌های کد با Drift

Drift از کلاس‌های گسترش‌دهنده Table یا حاشیه‌نویسی @DataClass استفاده می‌کند. در زیر — مثال کامل مدل Task با کوئری از طریق DSL، raw SQL و به‌روزرسانی واکنشگرا. پس از اجرای build_runner، تمام کلاس‌های تولید شده آماده هستند.

تعریف جدول و پایگاه داده

کلاس Tasks Table را گسترش می‌دهد و ستون‌ها را تعریف می‌کند. هر ستون عبارتی از نوع Column<T> است. پارامترها: withDefault() مقدار پیش‌فرض را تعیین می‌کند، autoIncrement() افزایش خودکار را فراهم می‌کند. پایگاه داده کلاس انتزاعی است که $DriftDatabase را گسترش می‌دهد.

dart
class Tasks extends Table {
    IntColumn get id => integer().autoIncrement();
    TextColumn get title => text().withDefault(const Constant(''))();
    BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
    IntColumn get priority => integer().withDefault(const Constant(0))();
}

@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
    AppDatabase(QueryExecutor e) : super(e);
}

عملیات CRUD از طریق DSL

Drift متدهای into(tasks).insert()، select(tasks)، update(tasks) و delete(tasks) را برای جداول تولید می‌کند. همه عملیات Future برمی‌گردانند — کار با SQLite ناهمزمان است. برای ردیابی تغییرات از .watch() به جای .get() استفاده کنید.

dart
// نوشتن
await into(tasks).insert(TasksCompanion.insert(
    title: Value('خرید محصولات'),
    priority: Value(3),
));

// خواندن با فیلتر
final highPriority = await (select(tasks)
    ..where((t) => t.priority.greaterThan(2))
    ..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
    .get();

// مشاهده واکنشگرا
select(tasks).watch().listen((tasksList) {
    // tasksList — List، با هر تغییر در جدول به‌روزرسانی می‌شود
    updateUi(tasksList);
});

Raw SQL با نتیجه تایپ‌شده

برای کوئری‌های پیچیده Drift اجازه نوشتن raw SQL را می‌دهد و همزمان تایپ‌سازی را حفظ می‌کند. متد customSelect رشته کوئری را دریافت کرده و نتیجه تایپ‌شده را از طریق مولد کد برمی‌گرداند. این رویکرد انعطاف‌پذیری SQL را با امنیت نوع Drift ترکیب می‌کند.

dart
final result = await customSelect(
    'SELECT title, COUNT(*) as cnt FROM tasks GROUP BY title',
    readsFrom: { tasks },
).get();

for (final row in result) {
    print('${row.readString("title")}: ${row.readInt("cnt")}');
}

مهاجرت‌ها و نسخه‌بندی در Drift

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

مهاجرت‌های خودکار

برای افزودن ستون با مقدار پیش‌فرض، Drift می‌تواند به طور خودکار از طریق MigrationStrategy مهاجرت ایجاد کند. اگر تغییر داده‌های موجود را نقض نکند (افزودن فیلد nullable)، می‌توان از beforeOpen با بررسی نسخه و اجرای ALTER TABLE استفاده کرد.

مهاجرت‌های دستی

برای تغییرات پیچیده (تغییر نام جدول، ادغام داده‌ها، تغییر نوع ستون) Drift نیاز به مهاجرت دستی SQL دارد. مهاجرت‌ها از طریق پارامتر migrations در کلاس پایگاه داده تعیین می‌شوند. هر مهاجرت شیئی با شماره‌های from/to و کوئری‌های SQL است.

Drift و آزمایش

Drift از اجرا در حالت آزمایشی از طریق NativeDatabase.memory() پشتیبانی می‌کند. پایگاه داده در حافظه قبل از هر تست از نو ایجاد شده و پس از آن نابود می‌شود. برای mock کردن از بسته mocktail با QueryExecutor تقلیدی استفاده کنید. Drift همچنین DatabaseTestHelper را برای تست‌های یکپارچه‌سازی با بررسی مهاجرت‌ها و کوئری‌ها فراهم می‌کند.

الگوی DAO در Drift

Drift از DAO (Data Access Object) از طریق کلاس‌های انتزاعی با حاشیه‌نویسی @DriftAccessor پشتیبانی می‌کند. DAO کوئری‌های یک یا چند جدول را کپسوله می‌کند و می‌تواند جدا از پایگاه داده آزمایش شود. برخلاف کوئری‌های مستقیم از طریق Database، DAO امکان استفاده مجدد از منطق کوئری بین بخش‌های مختلف برنامه را فراهم کرده و تست‌های ماژولار را ساده می‌کند.

dart
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
    AppDatabase(QueryExecutor e) : super(e) {
        migrations.add(Migration(1, 2, (m) async {
            await m.addColumn(tasks, tasks.dueDate);
            await m.createIndex(tasks.idxPriority);
        }));
    }
}

سؤالات متداول

Drift چه تفاوتی با Floor دارد؟

Drift به جای رشته‌های SQL از DSL مخصوص خود استفاده می‌کند که امنیت نوع کامل و تکمیل خودکار در IDE را فراهم می‌کند. Floor از رشته‌های SQL در حاشیه‌نویسی @Query استفاده می‌کند. Drift همچنین از پلتفرم‌های بیشتری (شامل وب) پشتیبانی کرده و واکنشگرایی داخلی از طریق Stream دارد، در حالی که در Floor Stream باید به صورت دستی اعلام شود.

آیا Drift از مهاجرت بدون از دست دادن داده پشتیبانی می‌کند؟

بله، Drift از مهاجرت با حفظ داده‌ها پشتیبانی می‌کند. برای افزودن ستون از addColumn در Migration استفاده کنید. برای تغییرات پیچیده (تغییر نام، ادغام) raw SQL در داخل مهاجرت بنویسید. اگر مهاجرت مشخص نشده باشد، Drift در صورت عدم تطابق طرح، پایگاه داده را با از دست دادن داده بازسازی می‌کند.

آیا می‌توان از Drift بدون build_runner استفاده کرد؟

Drift نیاز به تولید کد از طریق build_runner و drift_dev دارد. بدون تولید نمی‌توان کوئری‌های تایپ‌شده ایجاد کرد. با این حال، برای پروژه‌های کوچک Drift از sqlparser پشتیبانی می‌کند — نوشتن دستی فایل‌های SQL با تایپ‌سازی خودکار، اما این هنوز نیاز به مرحله تولید دارد.

چگونه Drift را با Riverpod یکپارچه کنیم؟

برای یکپارچه‌سازی با Riverpod از بسته drift_riverpod استفاده کنید. این بسته providerهایی برای Database، DAO و کوئری‌های Stream فراهم می‌کند. مثال: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — UI به طور خودکار هنگام تغییر داده بازسازی می‌شود.

آیا Drift از رمزگذاری SQLite پشتیبانی می‌کند؟

Drift رمزگذاری داخلی ندارد، اما از اتصال کتابخانه‌های سفارشی sqlite3 با SEE (SQLite Encryption Extension) پشتیبانی می‌کند. برای پلتفرم‌های موبایل از sqflite_sqlcipher به عنوان QueryExecutor استفاده کنید — Drift با هر پیاده‌سازی SQLite از طریق QueryExecutor انتزاعی کار می‌کند.

خلاصه

  • Drift — ORM واکنشگرا برای Flutter و Dart با کامپایل کوئری‌ها به SQL در مرحله build
  • نحو DSL — کوئری‌های Dart با امنیت نوع کامل و تکمیل خودکار در IDE
  • واکنشگرایی — Stream و کوئری‌های auto-updating برای به‌روزرسانی خودکار UI
  • چندسکویی — Android, iOS, Web (WASM), macOS, Linux, Windows
  • مهاجرت‌ها — خودکار برای تغییرات ساده و دستی SQL برای تغییرات پیچیده
  • اکوسیستم — یکپارچه‌سازی با Riverpod (drift_riverpod) و BLoC (drift_bloc)
  • توصیه — Drift را برای پروژه‌هایی انتخاب کنید که واکنشگرایی، امنیت نوع و پشتیبانی از تمام پلتفرم‌های Flutter مهم هستند

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید