Drift (Moor) — was es ist, reaktives ORM und Arbeit mit Datenbanken

Autor: IT Sectr Veröffentlicht: 2026-03-13 Lesezeit: 9 Min.

Drift (früher Moor) ist ein reaktives ORM für Flutter und Dart, das auf SQLite mit einem eigenen DSL für Abfragen aufbaut. Im Gegensatz zu traditionellen ORMs kompiliert Drift Dart-Abfragen zur Build-Zeit in SQL und vermeidet so Laufzeitfehler. Laut Drift Docs, 2024 generiert Drift bis zu 40% mehr Code als manuelle SQL-Abfragen, eliminiert aber das manuelle Schreiben von SQL vollständig und ersetzt es durch typsichere Dart-Syntax.

Wichtige Punkte

  • Drift — reaktives ORM mit Kompilierung von Abfragen in SQL zur Build-Zeit
  • DSL-Abfragen — flüssige Oberfläche in Dart ohne Schreiben von SQL-Strings
  • Reaktivität — Stream- und Auto-Update-Abfragen für Echtzeit-UI-Updates
  • Plattformübergreifend — Android, iOS, Web, macOS, Linux, Windows
  • Migrationen — automatische Versionierung und manuelle Migrationen per SQL

Was ist Drift?

Drift ist ein ORM für Dart und Flutter, früher bekannt als Moor. Es wurde 2019 von Simon Binder entwickelt und hat seitdem mehrere Hauptversionen durchlaufen. Drift kompiliert Dart-Abfragen zur Build-Zeit mit drift_dev und build_runner in SQL, was vollständige Typsicherheit bietet und SQL-Syntaxfehler zur Laufzeit eliminiert.

Im Gegensatz zu Floor verwendet Drift eine eigene DSL (Domain-Specific Language) zum Erstellen von Abfragen — der Entwickler schreibt in Dart, und der Generator übersetzt es in SQL. Dadurch kann die IDE die Syntax überprüfen, Tabellenfelder automatisch vervollständigen und das Datenmodell umgestalten, ohne Abfragen zu gefährden.

Laut Drift (2024) wird die Bibliothek in mehr als 8000 Flutter-Projekten verwendet. Sie unterstützt alle gängigen Plattformen: Android über sqflite, iOS über sqflite, Web über sqlite3 WASM, Desktop über nativen sqlite3-Treiber.

Umbenennungsgeschichte: Moor → Drift

Moor wurde in Version 2.0 (2022) in Drift umbenannt. Grund dafür war eine Namenskollision mit anderen Projekten und der Wunsch, sich vom alten Code zu distanzieren. Die API blieb kompatibel: Zur Migration genügt es, den Import von moor auf drift zu ändern und die Abhängigkeiten zu aktualisieren.

Schlüsselfunktionen

Drift bietet: integrierte Stream-Abfragen mit automatischer Aktualisierung bei Datenänderungen, Transaktionsunterstützung mit Rollback, benutzerdefinierte SQL-Abfragen per rawQuery, DAO-Muster zur Kapselung von Logik, plattformübergreifende Migrationen und Integration mit Riverpod und BLoC über die Pakete drift_riverpod und drift_bloc.

Wie funktioniert Drift?

Drift verwendet Codegenerierung zur Kompilierzeit. Der Entwickler beschreibt Tabellen durch @DataClass-Annotationen oder Dart-Klassen, die Table erweitern. Der Generator erstellt Hilfsklassen: Companion (für nullable Felder beim Einfügen/Aktualisieren), DriftDatabase (Einstiegspunkt) und DAO-Implementierungen.

Drift führt SQLite-Abfragen nicht direkt aus. Stattdessen schreibt der Entwickler in Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Der Generator übersetzt dies in SQL, und zur Laufzeit sendet Drift die fertige SQL-Abfrage einfach an SQLite. Dies kombiniert den Komfort der Dart-Syntax mit nativer SQL-Leistung.

Abfragearchitektur

Drift unterstützt zwei Abfragemodi: DSL (empfohlen) und rohes SQL. DSL-Abfragen sind sicherer zu schreiben — der Compiler prüft Feldnamen, Typen und Kompatibilität. Rohes SQL wird für komplexe Abfragen benötigt, die nicht von DSL abgedeckt werden: Fensterfunktionen, rekursive CTEs, spezifische SQLite-Erweiterungen.

Drift DSL vs SQL: Ansatzvergleich

Drift bietet zwei Möglichkeiten zum Schreiben von Abfragen: Dart DSL (nativ) und rohes SQL (für komplexe Fälle). DSL ist für 90% der Szenarien vorzuziehen: es ist sicherer, lesbarer und unterstützt Refactoring. Rohes SQL wird nur verwendet, wenn DSL die erforderliche Konstruktion nicht abdeckt.

AspektDrift DSLRohes SQL in Drift
TypsicherheitVollständig (Kompilierung)Nein (Laufzeit)
AutovervollständigungJa (IDE)Nur in sql-Dateien
RefactoringAutomatischManuelle Zeichenkettensuche
Komplexe JOINsUnterstütztVolle Freiheit
FensterfunktionenEingeschränktVolle Unterstützung
ReaktivitätIntegriert (Stream)Über .watch()

Wann DSL verwenden

Drift DSL ist die primäre Arbeitsweise. Es deckt SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN und Gruppierung ab. Für alle typischen CRUD-Abfragen verwenden Sie DSL: es ist kürzer, sicherer und aktualisiert Stream bei Änderungen automatisch.

Wann rohes SQL verwenden

Rohes SQL in Drift wird benötigt für: benutzerdefinierte SQLite-Funktionen (FTS5, JSON1), komplexe Unterabfragen mit EXISTS, INSERT OR REPLACE, Massen-UPDATE mit CASE sowie Abfragen, bei denen die Leistung kritisch ist und DSL keinen optimalen Ausführungsplan generiert. Rohes SQL kann in .sql-Dateien mit Typisierungsunterstützung über drift_dev geschrieben werden.

Codebeispiele mit Drift

Drift verwendet Klassen, die Table erweitern, oder die @DataClass-Annotation. Nachfolgend ein vollständiges Beispiel des Task-Modells mit Abfragen über DSL, rohes SQL und reaktive Aktualisierung. Nach dem Ausführen von build_runner sind alle generierten Klassen bereit.

Tabellen- und Datenbankdefinition

Die Klasse Tasks erweitert Table und definiert Spalten. Jede Spalte ist ein Ausdruck vom Typ Column<T>. Parameter: withDefault() setzt einen Standardwert, autoIncrement() setzt automatische Erhöhung. Die Datenbank ist eine abstrakte Klasse, die $DriftDatabase erweitert.

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-Operationen per DSL

Drift generiert die Methoden into(tasks).insert(), select(tasks), update(tasks) und delete(tasks) für Tabellen. Alle Operationen geben Future zurück — die Arbeit mit SQLite ist asynchron. Um Änderungen zu verfolgen, verwenden Sie .watch() anstelle von .get().

dart
// Einfügen
await into(tasks).insert(TasksCompanion.insert(
    title: Value('Lebensmittel kaufen'),
    priority: Value(3),
));

// Lesen mit Filter
final highPriority = await (select(tasks)
    ..where((t) => t.priority.greaterThan(2))
    ..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
    .get();

// Reaktive Überwachung
select(tasks).watch().listen((tasksList) {
    // tasksList — List, wird bei jeder Tabellenänderung aktualisiert
    updateUi(tasksList);
});

Rohes SQL mit typisiertem Ergebnis

Für komplexe Abfragen erlaubt Drift das Schreiben von rohem SQL unter Beibehaltung der Typisierung. Die Methode customSelect nimmt einen Abfragestring entgegen und gibt ein typisiertes Ergebnis über den Codegenerator zurück. Dieser Ansatz kombiniert die Flexibilität von SQL mit der Typsicherheit von 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")}');
}

Migrationen und Versionierung in Drift

Drift unterstützt sowohl automatische Migrationen (für einfache Änderungen) als auch manuelle (für komplexe Transformationen). Die Datenbankversion wird im AppDatabase-Konstruktor festgelegt. Bei Nichtübereinstimmung der Version wendet Drift alle ausstehenden Migrationen nacheinander an.

Automatische Migrationen

Um eine Spalte mit einem Standardwert hinzuzufügen, kann Drift automatisch eine Migration über MigrationStrategy generieren. Wenn die Änderung vorhandene Daten nicht beeinträchtigt (Hinzufügen eines nullable-Feldes), können Sie beforeOpen mit Versionsprüfung und Ausführung von ALTER TABLE verwenden.

Manuelle Migrationen

Für komplexe Änderungen (Umbenennen einer Tabelle, Zusammenführen von Daten, Ändern eines Spaltentyps) benötigt Drift eine manuelle SQL-Migration. Migrationen werden über den Parameter migrations in der Datenbankklasse angegeben. Jede Migration ist ein Objekt mit from/to-Nummern und SQL-Abfragen.

Drift und Tests

Drift unterstützt die Ausführung im Testmodus über NativeDatabase.memory(). Die In-Memory-Datenbank wird vor jedem Test von Grund auf neu erstellt und danach zerstört. Zum Mocken verwenden Sie das Paket mocktail mit einem gemockten QueryExecutor. Drift bietet außerdem DatabaseTestHelper für Integrationstests mit Migrations- und Abfrageüberprüfung.

DAO-Muster in Drift

Drift unterstützt DAO (Data Access Object) über abstrakte Klassen mit der @DriftAccessor-Annotation. DAO kapselt Abfragen an eine oder mehrere Tabellen und kann getrennt von der Datenbank getestet werden. Im Gegensatz zu direkten Abfragen über Database ermöglicht DAO die Wiederverwendung von Abfragenlogik zwischen verschiedenen Teilen der Anwendung und vereinfacht Unit-Tests.

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);
        }));
    }
}

Häufig gestellte Fragen

Wie unterscheidet sich Drift von Floor?

Drift verwendet ein eigenes DSL anstelle von SQL-Strings, was vollständige Typsicherheit und Autovervollständigung in der IDE bietet. Floor verwendet SQL-Strings in der @Query-Annotation. Drift unterstützt auch mehr Plattformen (einschließlich Web) und hat integrierte Reaktivität über Stream, während bei Floor Stream manuell deklariert werden muss.

Unterstützt Drift Migrationen ohne Datenverlust?

Ja, Drift unterstützt Migrationen mit Datenerhaltung. Verwenden Sie addColumn in Migration, um Spalten hinzuzufügen. Für komplexe Transformationen (Umbenennen, Zusammenführen) schreiben Sie rohes SQL innerhalb der Migration. Wenn keine Migration angegeben ist, erstellt Drift die Datenbank bei Nichtübereinstimmung des Schemas mit Datenverlust neu.

Kann Drift ohne build_runner verwendet werden?

Drift erfordert Codegenerierung über build_runner und drift_dev. Ohne Generierung ist es unmöglich, typisierte Abfragen zu erstellen. Für kleine Projekte unterstützt Drift jedoch sqlparser — manuelles Schreiben von SQL-Dateien mit automatischer Typisierung, aber dies erfordert dennoch einen Generierungsschritt.

Wie integriert man Drift mit Riverpod?

Verwenden Sie für die Integration mit Riverpod das Paket drift_riverpod. Es stellt Provider für Database, DAO und Stream-Abfragen bereit. Beispiel: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — die UI wird bei Datenänderungen automatisch neu aufgebaut.

Unterstützt Drift SQLite-Verschlüsselung?

Drift verfügt über keine integrierte Verschlüsselung, unterstützt aber das Verbinden benutzerdefinierter sqlite3-Bibliotheken mit SEE (SQLite Encryption Extension). Verwenden Sie für mobile Plattformen sqflite_sqlcipher als QueryExecutor — Drift arbeitet mit jeder SQLite-Implementierung über den abstrakten QueryExecutor.

Zusammenfassung

  • Drift — ein reaktives ORM für Flutter und Dart mit Kompilierung von Abfragen in SQL zur Build-Zeit
  • DSL-Syntax — Dart-Abfragen mit vollständiger Typsicherheit und Autovervollständigung in der IDE
  • Reaktivität — Stream- und Auto-Update-Abfragen für automatische UI-Updates
  • Plattformübergreifend — Android, iOS, Web (WASM), macOS, Linux, Windows
  • Migrationen — automatisch für einfache Änderungen und manuelles SQL für komplexe
  • Ökosystem — Integration mit Riverpod (drift_riverpod) und BLoC (drift_bloc)
  • Empfehlung — wählen Sie Drift für Projekte, bei denen Reaktivität, Typsicherheit und Unterstützung aller Flutter-Plattformen wichtig sind

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch