Prisma أم Drizzle: الاختيار يدور حول أين يعيش المخطط
أحدهما يولّد عميلًا من ملف مخطط يملكه؛ والآخر SQL بأنواع تكتبه أنت. والقرار يتعلق بالصياغة أقل مما يتعلق بمن يملك قاعدة بياناتك.
كلاهما جيد. فرق تسلّم تطبيقات جادّة بكل منهما، والجدال بينهما على الإنترنت أعلى صوتًا من الفرق في النتيجة.
وما يختلف فعلًا سؤال ينبغي أن تجيب عنه على أي حال: هل المخطط ملف في مستودعك تُولَّد منه قاعدة البيانات، أم أن قاعدة البيانات هي الحقيقة وشيفرتك رؤية بأنواع عليها؟
Prisma: ملف المخطط هو مصدر الحقيقة
تكتب schema.prisma، ويعطيك عميل مولَّد واجهة بأنواع.
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,
});فيمَ هو جيد. ملف واحد يصف النموذج كله، والعلاقات صريحة فيه. والعميل مريح وقابل للاستكشاف ويصعب إساءة استخدامه. والهجرات تُولَّد من الفرق بين المخطط وقاعدة البيانات، ما يزيل فئة كاملة من الأخطاء المكتوبة يدويًا. ولفريق لا يتقن SQL، ذلك فرق إنتاجية كبير وهو السبب الصادق لشعبيته.
أين يكلّفك. العميل المولَّد أثر حقيقي - يجب توليده في البناء، وهو ليس صغيرًا، ما يظهر في حزمة بلا خوادم وفي البدء البارد. والتجريد شامل بما يكفي ليشعر النزول تحته، حين تحتاج استعلامًا لا تعبّر عنه الواجهة، وكأنه مغادرة للأداة لا استخدام لها.
Drizzle: المخطط TypeScript والاستعلامات SQL
تصف الجداول بـ TypeScript، وبنّاء الاستعلامات قريب من SQL بما يكفي ليعلّمك قراءتُه الاستعلام.
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);فيمَ هو جيد. إنه رفيع. لا عميل مولَّد ولا محرك وقت تشغيل، فالحزمة صغيرة والبدء البارد أفضل - وهي الحجة التي تهم على منصة تحمل فيها كل دالة اعتمادياتها. ولأن بنّاء الاستعلامات ينعكس على SQL، يُكتب الاستعلام المعقّد بالأداة نفسها التي يُكتب بها البسيط بدل فتحة هروب.
أين يكلّفك. عليك أن تعرف SQL، وراحة التعامل مع العلاقات عمل أكثر من
include. وتوليد الهجرات موجود وأقل رأيًا. ولفريق يفضّل التفكير بالكائنات على
التفكير بالضمّ، فيه احتكاك أكبر لكل استعلام.
السؤال الذي يقرره
ليس الصياغة. اسأل من يملك المخطط.
إن كان يملكه التطبيق - أرض جديدة، فريق واحد، وقاعدة البيانات موجودة لخدمة هذه الشيفرة - فملف مخطط يولّد قاعدة البيانات ترتيب نظيف، ونموذج Prisma يناسبه تمامًا.
وإن كانت تملكه قاعدة البيانات - أقدم من التطبيق، وأنظمة أخرى تكتب فيها، ولمدير قواعد البيانات آراء، وفيها views ومحفّزات وأشياء لم ينشئها ORM لديك - فأداة تصف ما هو موجود تناسب أكثر من أداة تريد تعريفه.
والحالة الثانية أشيع مما توحي به كتابات الأرض الجديدة، وفيها يبدأ ORM قائم على المخطط في الشعور بأنه يقاومك.
الجزء الخاص بـ Next.js
أمران يهمّان هنا ولم يكونا ليهمّا على خادم طويل العمر.
الحزمة والبدء البارد. العميل الأثقل في كل نسخة دالة يُدفع ثمنه عند كل بدء بارد. تلك نقطة حقيقية لصالح الخيار الأرفع، وهي أصغر من استعلام واحد سيئ الشكل. قِس مسارك أنت قبل أن تعدّها حاسمة.
أين يُنشأ العميل. متطابق للاثنين وأهم من الاختيار: نسخة لكل نطاق وحدة، لا لكل طلب. أخطئ في ذلك مع أيّهما وستكون تقرأ عن حدود الاتصالات لا عن أنظمة ORM.
وإن كنت تنشر على بيئة الحافة، افحص مسار المشغّل لا الـ ORM - كلاهما يعمل هناك عبر مشغّل قادر على HTTP، وكلاهما لا يعمل عبر مشغّل TCP.
ما الذي يسوء فعلًا، مع أيّهما
ليس الـ ORM أبدًا. في كل شيفرة سُلّمت إلينا، كانت مشكلات طبقة البيانات القائمة القصيرة نفسها: استعلام داخل حلقة، وصفحة تجلب البيانات نفسها في ثلاثة مكوّنات لأن أحدًا لم يزل التكرار، ولا فهرس خلف مرشِّح يتحكم به مستخدم، وعميل يُنشأ لكل طلب.
الأربعة ممكنة في الأداتين وغير مرئية في الأداتين. أيًّا اخترت، فما يستحق إضافته في الأسبوع الأول هو وسيلة لرؤية عدد استعلامات مسار - لأن ذلك الرقم هو ما ستصحّحه فعلًا، ولا يغيّره أي من الاختيارين.
