Prisma oder Drizzle: die Frage ist, wo das Schema lebt
Eines erzeugt einen Client aus einer Schemadatei, die ihm gehört; das andere ist typisiertes SQL, das Sie schreiben. Die Entscheidung dreht sich weniger um Syntax als darum, wem Ihre Datenbank gehört.
Beide sind gut. Teams liefern mit jedem davon ernsthafte Anwendungen aus, und der Streit darüber im Netz ist lauter als der Unterschied im Ergebnis.
Was sich tatsächlich unterscheidet, ist eine Frage, die Sie ohnehin beantworten sollten: Ist das Schema eine Datei in Ihrem Repository, aus der die Datenbank erzeugt wird, oder ist die Datenbank die Wahrheit und Ihr Code eine typisierte Sicht darauf?
Prisma: die Schemadatei ist die Quelle der Wahrheit
Sie schreiben schema.prisma, und ein erzeugter Client gibt Ihnen eine
typisierte API.
model Order {
id String @id @default(cuid())
total Int
customer Customer @relation(fields: [customerId], references: [id])
customerId String
createdAt DateTime @default(now())
}const orders = await db.order.findMany({
where: { total: { gt: 5000 } },
include: { customer: true },
take: 20,
});Worin es gut ist. Eine Datei beschreibt das ganze Modell, und die Beziehungen stehen explizit darin. Der Client ist angenehm, auffindbar und schwer falsch zu benutzen. Migrationen werden aus der Differenz zwischen Schema und Datenbank erzeugt, was eine ganze Kategorie handgeschriebener Fehler entfernt. Für ein Team, das nicht flüssig SQL spricht, ist das ein großer Produktivitätsunterschied und der ehrliche Grund seiner Beliebtheit.
Wo es Sie kostet. Der erzeugte Client ist ein echtes Artefakt - er muss im Build erzeugt werden und ist nicht klein, was sich in einem Serverless-Bundle und im Kaltstart zeigt. Und die Abstraktion ist gründlich genug, dass es sich wie Verlassen des Werkzeugs anfühlt, unter sie zu greifen, wenn Sie eine Abfrage brauchen, die die API nicht ausdrückt.
Drizzle: das Schema ist TypeScript und die Abfragen sind SQL
Sie beschreiben Tabellen in TypeScript, und der Query Builder ist nah genug an SQL, dass ihn zu lesen die Abfrage lehrt.
export const orders = pgTable('orders', {
id: text('id').primaryKey(),
total: integer('total').notNull(),
customerId: text('customer_id').notNull().references(() => customers.id),
createdAt: timestamp('created_at').defaultNow(),
});
const rows = await db
.select()
.from(orders)
.where(gt(orders.total, 5000))
.limit(20);Worin es gut ist. Es ist dünn. Es gibt keinen erzeugten Client und keine Laufzeit-Engine, das Bundle ist also klein und der Kaltstart besser - das Argument, das auf einer Plattform zählt, wo jede Funktion ihre Abhängigkeiten trägt. Und weil der Query Builder auf SQL abbildet, wird eine komplexe Abfrage im selben Werkzeug geschrieben wie eine einfache statt in einer Notluke.
Wo es Sie kostet. Sie müssen SQL können, und die Ergonomie von Beziehungen
ist mehr Arbeit als include. Migrationserzeugung existiert und ist weniger
meinungsstark. Für ein Team, das lieber in Objekten als in Joins denkt, ist es
mehr Reibung je Abfrage.
Die Frage, die es entscheidet
Nicht Syntax. Fragen Sie, wem das Schema gehört.
Gehört es der Anwendung - grüne Wiese, ein Team, die Datenbank existiert für diese Codebasis -, ist eine Schemadatei, die die Datenbank erzeugt, eine saubere Anordnung, und Prismas Modell passt genau dazu.
Gehört es der Datenbank - sie ist älter als die Anwendung, andere Systeme schreiben hinein, ein DBA hat Meinungen, es gibt Views und Trigger und Dinge, die Ihr ORM nicht erzeugt hat -, dann passt ein Werkzeug, das beschreibt, was da ist, besser als eines, das es definieren will.
Der zweite Fall ist häufiger, als Texte über grüne Wiesen vermuten lassen, und dort beginnt sich ein Schema-First-ORM anzufühlen, als kämpfe es gegen Sie.
Der Next.js-spezifische Teil
Zwei Dinge zählen hier, die auf einem langlebigen Server nicht zählen würden.
Bundle und Kaltstart. Ein schwererer Client in jeder Funktionsinstanz wird bei jedem Kaltstart bezahlt. Das ist ein echter Punkt für die dünnere Option, und er ist kleiner als eine einzige schlecht geformte Abfrage. Messen Sie Ihre eigene Route, bevor Sie ihn für entscheidend halten.
Wo der Client erzeugt wird. Für beide identisch und wichtiger als die Wahl: eine Instanz je Modulbereich, nicht eine je Anfrage. Machen Sie das mit einem von beiden falsch, und Sie lesen über Verbindungslimits statt über ORMs.
Liefern Sie in die Edge-Laufzeit aus, prüfen Sie den Treiberpfad statt des ORMs - beide funktionieren dort über einen HTTP-fähigen Treiber, und beide nicht über einen TCP-Treiber.
Was tatsächlich schiefgeht, mit beiden
Es ist nie das ORM. In jeder Codebasis, die uns übergeben wurde, waren die Probleme der Datenschicht dieselbe kurze Liste: eine Abfrage in einer Schleife, eine Seite, die dieselben Daten in drei Komponenten lädt, weil niemand dedupliziert hat, kein Index hinter einem Filter, den ein Nutzer steuert, und ein Client, der je Anfrage erzeugt wird.
Alle vier sind in beiden Werkzeugen möglich und in beiden unsichtbar. Was Sie auch wählen, das Lohnende in Woche eins ist eine Möglichkeit, die Abfragezahl einer Route zu sehen - denn diese Zahl ist das, was Sie tatsächlich debuggen werden, und keine der beiden Entscheidungen ändert sie.
