ما هي أداة ORM المناسبة لاستخدامها في GO: GORM، sqlc، Ent أو Bun؟

GORM مقابل sqlc مقابل Ent مقابل Bun ```

Page content

تُقدّم بيئة Go مجموعة متنوعة من أدوات ORM (Object-Relational Mapping) ومكتبات قواعد البيانات، كل منها له فلسفته الخاصة. إليك مقارنة شاملة لأربعة حلول رئيسية لـ استخدام PostgreSQL في Go: GORM، sqlc، Ent، وBun.

لنقيّمها من حيث الأداء، تجربة المطور، الشعبيّة، والمزايا/التوسع، مع أمثلة على الكود التي تُظهر العمليات الأساسية CRUD على نموذج User. سيحصل المطورون المتقدمون والمتوسطون في Go على رؤية حول التنازلات لكل أداة وما قد يناسب احتياجاتهم بشكل أفضل.

شاشة كمبيوتر مع بعض الكود

ملخص حول ORMs

  • GORM – ORM غني بالميزات من نوع Active Record. يسمح لك GORM بتعريف هيكل Go كنموذج ويقدم واجهة برمجة تطبيقات واسعة لاستعلام البيانات وإدارتها باستخدام التسلسل. لقد كان موجودًا منذ سنوات وهو أحد أكثر ORMs استخدامًا في Go (مع حوالي 39 ألف نجمة على GitHub). يجذب GORM ميزاته القوية (التحديثات التلقائية، إدارة العلاقات، التحميل المبكر، المعاملات، والوظائف، وغيرها) والتي تمكنك من كتابة كود Go نظيفًا مع أقل كمية من SQL الخام. ومع ذلك، فإنه ي引入 أنماطه الخاصة ويؤدي إلى تكاليف تشغيلية بسبب استخدام الانعكاس والواجهات، مما قد يؤثر على الأداء في العمليات الكبيرة.

  • sqlc – ليس ORM تقليديًا بل هو مُولّد للكود SQL. مع sqlc، تكتب استعلامات SQL بسيطة (في ملفات .sql)، ويقوم الأداة بتحفيز كود Go آمن من الناحية النوعية (وظائف DAO) لتنفيذ هذه الاستعلامات. هذا يعني أنك تتفاعل مع قاعدة البيانات من خلال استدعاء وظائف Go المُولدة (مثل CreateUser، GetUser) وتتلقى نتائجًا مُصنفة دون الحاجة إلى مسح الصفوف يدويًا. sqlc يسمح لك باستخدام SQL الخام مع أمان في وقت التجميع – لا يوجد طبقة لبناء الاستعلامات أو الانعكاس في وقت التشغيل. وقد أصبح شائعًا (~16k نجمة) لـ البساطة والأداء: يتجنب التكاليف التشغيلية تمامًا من خلال استخدام SQL المُعدّ، مما يجعله بنفس السرعة عند استخدام database/sql مباشرة. التنازل هو أنك يجب أن تكتب (وتُحافظ على) استعلامات SQL الخاصة بك، ويمكن أن تكون منطق الاستعلامات الديناميكية أقل وضوحًا مقارنة بالـ ORMs التي تبني SQL لك.

  • Ent (Entgo) – ORM من نوع النموذج أولاً يستخدم إنشاء الكود لخلق واجهة برمجة آمنة من الناحية النوعية لنموذج البيانات. تعرّف على نموذجك في Go (باستخدام DSL الخاص بـ Ent للحقول، والروابط/العلاقات، والقيود، وغيرها)، ثم يُنشئ Ent حزم Go للنماذج ووظائف الاستعلام. الكود المُنشئ يستخدم واجهة برمجة تطبيقات مُ流ّة لبناء الاستعلامات، مع فحص نوعي كامل في وقت التجميع. Ent هو نسخة أحدث (مدعوم من مؤسسة Linux وتم تطويره بواسطة Ariga). يركز Ent على تجربة المطور والدقة – يتم بناء الاستعلامات عبر طرق قابلة للتسلسل بدلًا من السلاسل الخام، مما يجعلها أسهل في التكوين وقلّة الأخطاء. تُنتج طريقة Ent استعلامات SQL مُحسّنة للغاية، و حتى تُخزن نتائج الاستعلامات لتقليل الزيارات المكررة إلى قاعدة البيانات. تم تصميمه مع مراعاة التوسع، لذا فإنه يتعامل مع النماذج المعقدة والروابط بكفاءة. ومع ذلك، فإن استخدام Ent يضيف خطوة بناء (تشغيل إنشاء الكود) ويُربّطك ببيئته. في الوقت الحالي، يدعم Ent PostgreSQL، MySQL، SQLite (ودعم تجريبي لقواعد بيانات أخرى)، بينما يدعم GORM نطاقًا أوسع من قواعد البيانات (بما في ذلك SQL Server وClickHouse).

  • Bun – ORM أحدث مع نهج “SQL أولاً”. تم إلهام Bun من Go database/sql ومكتبة go-pg الأقدم، بهدف أن يكون خفيفًا وسريعًا مع التركيز على ميزات PostgreSQL. بدلًا من نمط Active Record، يوفر Bun مُنشئ استعلامات مُلتف: تبدأ الاستعلام مع db.NewSelect() / NewInsert() / وغيرها، وبناءها مع طرق تشبه SQL (يمكنك حتى إدراج أجزاء SQL خام). يقوم بربط نتائج الاستعلام إلى هيكل Go باستخدام علامات هيكل تشبه تلك الموجودة في GORM. يفضل Bun الوضوح ويسمح بالعودة إلى SQL الخام بسهولة عند الحاجة. لا يقوم Bun بتشغيل التحديثات التلقائية (يكتب التحديثات أو يستخدم حزمة التحديثات الخاصة بـ Bun يدويًا)، وهو ما يراه البعض ميزة للتحكم. يُمدح Bun لتعامله باستطاعة مع الاستعلامات المعقدة ودعمه لأنواع Postgres المتقدمة (المصفوفات، JSON، وغيرها) بشكل افتراضي. في الممارسة العملية، فإن Bun يفرض أقل تكاليف تشغيلية مقارنة بـ GORM (لا يوجد انعكاس ثقيل في كل استعلام)، مما يترجم إلى أداء أفضل في السيناريوهات ذات الحمولة العالية. مجتمعه أصغر (~4k نجمة)، ومجموعة ميزاته، رغم أنها جيدة، ليست بنفس الاتساع كما هو الحال في GORM. قد يتطلب Bun بعض المعرفة الإضافية في SQL لاستخدامه بشكل فعّال، لكنه يكافئ ذلك بالأداء والمرونة.

معايير الأداء

عندما يتعلق الأمر بالأداء (مدة الاستعلام والتدفق)، هناك اختلافات كبيرة في سلوك هذه الأدوات، خاصة مع زيادة الحمولة. تشير المعايير الحديثة والتجربة المستخدمية إلى بعض النقاط الرئيسية:

  • GORM: يُقدّم GORM راحة استخدام على حساب بعض الأداء. بالنسبة للعمليات البسيطة أو مجموعات النتائج الصغيرة، يمكن أن يكون GORM سريعًا جدًا – في الواقع، أظهرت إحدى المعايير أن GORM كان لديه أسرع وقت تنفيذ للاستعلامات الصغيرة جدًا (مثل استرداد 1 أو 10 سجلات). ومع ذلك، مع زيادة عدد السجلات، تؤدي تكاليف GORM إلى تأخير كبير. في اختبار استرداد 15,000 سجل، كان GORM بطيئًا تقريبًا ضعفًا مقارنة بـ sqlc أو حتى database/sql مباشرة (59.3 مللي ثانية لـ GORM مقابل ~31.7 مللي ثانية لـ sqlc و32.0 مللي ثانية لـ database/sql في هذه الحالة). التصميم المعتمد على الانعكاس والتعقيدات المجردة لبناء الاستعلامات تضيف تأخيرًا، وربما يصدر GORM عدة استعلامات لحمولات معقدة (مثل استعلام واحد لكل كيان مرتبط عند استخدام Preload بشكل افتراضي)، مما يمكن أن يزيد التأخير. باختصار: GORM عادةً ما يكون مناسبًا للحمولات المعتدلة، ولكن في سيناريوهات التدفق العالية أو العمليات الكبيرة، يمكن أن تصبح تكاليفه عائقًا.

  • sqlc: لأن استدعاءات sqlc تشبه SQL مكتوبة يدويًا، فإن أداءها مماثل لاستخدام المكتبة القياسية مباشرة. تضع المعايير sqlc بين أسرع الخيارات، غالبًا ما تكون فقط ببطء بسيط أو حتى أسرع قليلاً من database/sql لعمليات مماثلة. على سبيل المثال، عند استرداد 10k+ سجل، لاحظوا أن sqlc تتفوق قليلاً على database/sql في التدفق (ربما بسبب تجنب بعض المهام المكررة لمسح البيانات واستخدام إنشاء الكود الفعّال). مع sqlc، لا توجد واجهة برمجة تطبيقات لبناء الاستعلامات في وقت التشغيل – يتم تنفيذ الاستعلام كبيان SQL مُعدّ مع تكاليف قليلة. المفتاح هو أنك بالفعل قمت بكتابة استعلام SQL مثالي، وsqlc تساعدك فقط على تجنب الجهد المتعب لتحويل الصفوف إلى أنواع Go. في الممارسة العملية، هذا يعني التوسع الممتاز – sqlc ستتعامل مع تحميل البيانات الكبيرة بنفس الكفاءة كما تفعل SQL الخام، مما يجعلها خيارًا رائدًا عندما تكون السرعة المطلقة حاسمة.

  • Ent: يقع أداء Ent بين النهج القائم على SQL الخام والـ ORMs التقليدية. لأن Ent تُنشئ كودًا آمنًا من الناحية النوعية، فإنها تتجنب الانعكاس وتكاليف تكوين الاستعلامات في وقت التشغيل. SQL الذي تنتجه Ent مُحسّن (يمكنك التحكم في كتابة استعلامات فعّالة عبر واجهة Ent)، وتحتوي Ent على طبقة تخزين داخليّة لاستخدام خطط تنفيذ الاستعلامات/النتائج في بعض الحالات. هذا يمكن أن يقلل من الزيارات المتكررة إلى قاعدة البيانات في سيناريوهات معقدة. على الرغم من أن المعايير المحددة لـ Ent مقارنة بغيرها تختلف، فإن العديد من المطورين يبلغون أن Ent تتفوق على GORM في العمليات المكافئة، خاصة مع زيادة التعقيد. أحد الأسباب هو أن GORM قد تنتج استعلامات غير مثالية (مثل N+1 استعلامات إذا لم تستخدم المراجعات/الإدراجات بعناية)، بينما Enc تشجع على تحميل البيانات المبكر بشكل صريح وستدمج البيانات في عدد أقل من الاستعلامات. Enc أيضًا تميل إلى تخصيص ذاكرة أقل لكل عملية مقارنة بـ GORM، مما يمكن أن يحسن التدفق من خلال تقليل الضغط على جمع القمامة في Go. بشكل عام، تم بناء Ent للاستهلاك العالي للأداء والنموذج المعقد – تكاليفها منخفضة، ويمكنها التعامل مع الاستعلامات المعقدة بكفاءة – لكنها قد لا تساوي أداء SQL مكتوبة يدويًا (sqlc) في كل السيناريوهات. إنها خيار قوي إذا كنت ترغب في السرعة والسلامة من طبقة ORM.

  • Bun: تم إنشاء Bun مع التركيز على الأداء، وغالبًا ما يُعتبر بديلاً أسرع لـ GORM. تستخدم Bun واجهة برمجة تطبيقات مُلتفة لبناء استعلامات SQL، لكن هذه المُنشئات خفيفة. لا تخفف Bun عن SQL من أمامك – إنها طبقة رقيقة فوق database/sql، مما يعني أنك تتحمل تكاليف قليلة فقط فوق ما تفعله مكتبة Go القياسية. لاحظ المطورون تحسينات كبيرة عند الانتقال من GORM إلى Bun في المشاريع الكبيرة: على سبيل المثال، ذكرت تقرير أن GORM كان “مُبطئًا جدًا” لحجمهم، وحلّت مشكلة الأداء بتحويلها إلى Bun (مع بعض SQL الخام). في معايير مثل go-orm-benchmarks، تميل Bun إلى البقاء في الأعلى من حيث السرعة لمعظم العمليات (غالبًا ضمن 1.5× من sqlx/sql الخام) وتتفوق على GORM بشكل كبير في التدفق. كما أنها تدعم الإدراجات بالجملة، التحكم اليدوي في المراجعات مقابل الاستعلامات الفردية، وغيرها من التحسينات التي يمكن للمطورين الاستفادة منها. الخلاصة: تقدم Bun أداءً قريبًا من الأداء المعدّ يدويًا، وهي خيار رائع عندما ترغب في راحة ORM دون التضحية بالسرعة. طبيعتها القائمة على SQL تضمن أنك دائمًا يمكنك تحسين الاستعلامات إذا لم تكن المُولدة مثالية.

ملخص الأداء: إذا كان الهدف هو الأداء القصوى (مثل التطبيقات المكثفة للبيانات، الخدمات الميكرو تحت الضغط العالي)، فإن sqlc أو حتى استدعاءات database/sql مكتوبة يدويًا هي الفائزين. لديها تكاليف تقريبًا صفرية للإخفاء وتنمو بشكل خطي. Ent وBun أيضًا تؤدي بشكل جيد ويمكنها التعامل مع الاستعلامات المعقدة بكفاءة – تحقق توازنًا بين السرعة والإخفاء. GORM توفر أكبر عدد من الميزات ولكن على حساب التكاليف؛ إنها مقبولة تمامًا للكثير من التطبيقات، ولكن يجب أن تكون واعيًا بتأثيرها إذا كنت تتوقع التعامل مع كميات كبيرة من البيانات أو تحتاج إلى تأخير منخفض جدًا. (في هذه الحالات، يمكنك تقليل تكاليف GORM من خلال استخدام Joins بدلاً من Preload بعناية لتقليل عدد الاستعلامات، أو من خلال مزج SQL الخام في المسارات الحرجة، لكن ذلك يضيف تعقيدًا.)

تجربة المطور وسهولة الاستخدام

تجربة المطور يمكن أن تكون ذاتية، لكنها تشمل منحنى التعلم، ووضوح واجهة برمجة التطبيقات، وكيفية الإنتاجية في العمل اليومي. إليك كيف تقارن أربعة منافسين لدينا من حيث سهولة الاستخدام وتدفق التطوير:

  • GORM – غني بالميزات لكنه يتطلب منحنى تعلم: غالبًا ما يكون GORM أول ORM يجربه المطورون الجدد في Go لأنه يضمن تجربة تلقائية تمامًا (تعرّف على الهياكل وتستطيع إنشاء/العثور على البيانات فورًا دون كتابة SQL). التوثيق ممتاز جدًا مع الكثير من الدليل، مما يساعد. ومع ذلك، فإن GORM لديها طريقة مميزة لفعل الأمور (“نهج القائمة على الكود” للتفاعل مع قاعدة البيانات) ويمكن أن تبدو مُحرجة في البداية إذا كنت معتادًا على SQL الخام. العديد من العمليات الشائعة بسيطة (مثل db.Find(&objs))، لكن عندما تغوص في العلاقات، العلاقات متعددة الأشكال، أو الاستعلامات المتقدمة، يجب أن تتعلم تقنيات GORM (علامات الهياكل، Preload مقابل Joins، وغيرها). هذا هو السبب في أن هناك غالبًا ذكرًا لـ منحنى تعلم ملحوظ. بمجرد أن تسلّم هذا المنحنى، يمكن أن يكون GORM منتجًا جدًا – إنك تقضي وقتًا أقل في كتابة SQL مكرر ووقتًا أكثر في منطق Go. في الواقع، بعد إتقانه، يجد العديد من المطورين أنهم يمكنهم تطوير الميزات أسرع مع GORM من المكتبات الأدنى. باختصار، تجربة GORM: عالية في التعقيد الأولي ولكن تصبح سلسة مع الخبرة. المجتمع الكبير يعني وجود أمثلة واسعة وحلول StackOverflow متاحة، وبيئة إضافية غنية يمكن أن تبسط المهام (على سبيل المثال، إضافات لحذف ناعم، المراجعة، وغيرها). الملاحظة الرئيسية هي أنك يجب أن تكون واعيًا بما يفعله GORM تحت الغطاء لتجنب الأخطاء (مثل الاستعلامات N+1 غير المقصودة). لكن بالنسبة للمطور الذي يخصص وقتًا لـ GORM، فإنها بالفعل “تبدو” كحل قوي متكامل يتعامل مع الكثير من الأمور لك.

  • Ent – نموذج أولًا وآمن من الناحية النوعية: تم تصميم Ent بشكل صريح لتحسين تجربة المطور مع ORMs. تصفّر نموذجك في مكان واحد (كود Go) وتتلقى واجهة برمجة آمنة من الناحية النوعية للعمل معها – هذا يعني لا استعلامات مبنية على السلاسل، وغالبًا ما يتم التقاط الأخطاء في وقت التجميع. على سبيل المثال، إذا حاولت الإشارة إلى حقل أو علاقة غير موجودة، فإن كودك ببساطة لن يتم تجميعه. هذا الشبكة الأمان هي فوز كبير لتجربة المطور في المشاريع الكبيرة. واجهة Ent لبناء الاستعلامات مُلتفة وواضحة: تسلسل الطرق مثل .Where(user.EmailEQ("alice@example.com")) وتشبه القراءة باللغة الإنجليزية. يجد المطورون من ORMs في لغات أخرى أن نهج Ent طبيعي (متشابه إلى حد ما مع Entity Framework أو Prisma، لكن في Go). تعلم Ent يتطلب فهم سير عمل إنشاء الكود. ستحتاج إلى تشغيل entc generate (أو استخدام go generate) كلما قمت بتعديل نموذجك. هذا خطوة إضافية، لكنها غالبًا جزء من عملية البناء. منحنى التعلم لـ Ent معتدل – إذا كنت تعرف Go ومبادئ SQL، فإن مفاهيم Ent (الحقول، الروابط، وغيرها) بسيطة. في الواقع، يجد العديد من المطورين أن Ent أسهل في الفهم من GORM، لأنك لا تحتاج إلى التساؤل عن ما SQL يتم إنتاجه؛ يمكنك غالبًا التنبؤ به من أسماء الطرق، ويمكنك إخراج الاستعلام للتحقق منه إذا لزم الأمر. توثيق Ent و أمثلته جيدة، وكونه مدعوم من شركة تضمن أنه يتم الحفاظ عليه نشطًا. تجربة المطور مع Ent: جيدة جدًا لمن يرغب في الوضوح والأمان. الكود الذي تكتبه أكثر تفصيلًا مقارنة بـ SQL الخام، لكنه مُوثق ذاتيًا. أيضًا، إعادة التصميم أكثر أمانًا (إعادة تسمية حقل في النموذج وإعادة إنشاء الكود ستقوم بتحديث جميع الاستخدامات في الاستعلامات). الجانب السلبي قد يكون أنك محدود إلى حد ما بما يوفره إنشاء الكود في Ent – إذا كنت بحاجة إلى استعلام مخصص جدًا، فربما تكتبه مع واجهة Ent (ويمكنها التعامل مع المراجعات المعقدة، لكن أحيانًا قد تجد حالة أسهل في كتابتها بـ SQL الخام). Ent تسمح بقطع SQL خام عند الحاجة، لكن إذا وجدت نفسك تفعل ذلك غالبًا، فقد تتساءل إذا كان ORM هو الخيار المناسب. باختصار، يوفر Ent تجربة مطور سلسة لمعظم منطق CRUD والاستعلامات مع أقل المفاجآت، طالما أنك موافق على تشغيل خطوة إنشاء الكود والالتزام بالأنماط التي يفرضها Ent.

  • Bun – أقرب إلى SQL، أقل سحرًا: استخدام Bun يختلف عن استخدام GORM أو Ent. فلسفته هي عدم إخفاء SQL — إذا كنت تعرف SQL، فأنت بالفعل تعرف كيفية استخدام Bun في معظم الأجزاء. على سبيل المثال، لاستعلام المستخدمين قد تكتب: db.NewSelect().Model(&users).Where("name = ?", name).Scan(ctx). هذه واجهة برمجة تطبيقات مُلتفة لكنها قريبة جدًا من هيكل بيان SELECT الفعلي. منحنى التعلم لـ Bun عمومًا منخفض إذا كنت معتادًا على SQL نفسه. هناك أقل “سحر ORM” لتعلم؛ تتعلم غالبًا أسماء الطرق لبناء الاستعلامات وعادات علامات الهيكل. هذا يجعل Bun مناسبًا جدًا للمطورين المتمرسين الذين استخدموا database/sql أو مُنشئات الاستعلامات الأخرى مثل sqlx. في المقابل، قد يجد المبتدئ الذي لا يثق بكتابة SQL أن Bun أقل مساعدة من GORM – لن يقوم Bun تلقائيًا بإنشاء استعلامات معقدة لك عبر العلاقات إلا إذا حددتها. ومع ذلك، يتم دعم العلاقات والتحميل المبكر (Relation() طريقة للربط الجداول) – فقط يفعل ذلك صراحة، وهو ما يفضله بعض المطورين لوضوحها. تجربة المطور مع Bun يمكن وصفها بأنها خفيفة ومتوقعة. تكتب قليلاً من الكود أكثر من GORM في بعض العمليات (بما أن Bun غالبًا ما تطلب منك تحديد الأعمدة أو المراجعات صراحة)، لكن في المقابل تحصل على أكثر سيطرة ووضوح. هناك قليل من السحر الداخلي، لذا يكون التصحيح أسهل (يمكنك عادة تسجيل سلسلة الاستعلام التي بنتها Bun). توثيق Bun (دليل uptrace.dev) شامل ويحتوي على أنماط للمigrations، المعاملات، وغيرها، لكن المجتمع الأصغر يعني عددًا أقل من الدروس التدريبية من الطرف الثالث. جانب آخر من تجربة المطور هو الأدوات المتاحة: كون Bun مجرد توسعة لـ database/sql يعني أن أي أداة تعمل مع sql.DB (مثل وكلاء التصحيح، مسجلات الاستعلامات) تعمل مع Bun بسهولة. باختصار، يوفر Bun تجربة لا تهتم بالتفاصيل: تشعر وكأنك تكتب SQL منظمة في Go. هذا رائع للمطورين الذين يقدّرون السيطرة والأداء، لكنه قد لا يساعد المطورين الأقل خبرة بنفس القدر كما يفعل GORM. يتفق الجميع على أن Bun يجعل الأمور البسيطة سهلة والأمور المعقدة ممكنة (كما هو الحال مع SQL الخام)، دون فرض الكثير من الإطار علىك.

  • sqlc – اكتب SQL، احصل على كود Go: تقلب sqlc نهج ORM المعتاد. بدلًا من كتابة كود Go لإنتاج SQL، تكتب SQL وتحصل على كود Go. للمطورين الذين يحبون SQL، هذا رائع – يمكنك استخدام كل قوة SQL (المراجعات المعقدة، CTEs، وظائف النافذة، وغيرها) مع عدم وجود أي قيود ORM. منحنى التعلم لـ sqlc نفسه صغير جدًا. إذا كنت تعرف كيفية كتابة SELECT/INSERT/UPDATE في SQL، فأنت بالفعل أكملت الجزء الصعب. تحتاج إلى تعلم كيفية تضمين الاستعلامات مع تعليقات -- name: Name :one/many/exec وضبط ملف الإعداد، لكن هذا بسيط جدًا. الكود المُنشئ سيكون تعريفات وظائف مباشرة، والتي تُستدعى مثل أي وظيفة Go. مزايا تجربة المطور: لا توجد واجهة برمجة تطبيقات ORM لتعلمها، لا مفاجآت – تنفذ الاستعلامات بالضبط كما تم كتابتها. تتجنب فئة كاملة من مشاكل ORMs (مثل معرفة سبب إنتاج ORM معين JOIN أو كيفية تعديل استعلام). أيضًا، يمكن أن تشمل مراجعة الكود مراجعة SQL نفسها، والتي غالبًا تكون أكثر وضوحًا لمنطق معقد. ميزة DX كبيرة أخرى: الأمان من الناحية النوعية والدعم من IDE – يمكن للوظائف والهيكل المُنشئ أن تُستخدم في محررك، تعمل أدوات إعادة التصميم عليها، وغيرها، بعكس الاستعلامات الخام التي تكون غير واضحة لـ IDEs. من ناحية السلبيات، sqlc تطلب منك إدارة ملفات SQL. هذا يعني أنه إذا تغيرت نموذجك أو متطلباتك، فعليك تحديث أو إضافة الاستعلامات ذات الصلة يدويًا وإعادة تشغيل إنشاء الكود. ليس صعبًا، لكنه عمل يدوي أكثر من مجرد استدعاء وظيفة ORM. أيضًا، الاستعلامات الديناميكية (حيث أجزاء من SQL معطاة بشكل شرطي) قد تكون مرهقة – إما أن تكتب عدة نسخ من الاستعلام أو تستخدم تقنيات SQL. يذكر بعض المطورين أن هذا هو محدودية نهج sqlc. في الممارسة العملية، يمكنك غالبًا ترتيب الوصول إلى البيانات بحيث لا تحتاج إلى SQL ديناميكية أكثر من اللازم، أو يمكنك استدعاء database/sql الخام لهذه الحالات الخاصة. لكن هذا هو اعتبار: sqlc ممتازة للاستعلامات المُعرفة جيدًا، أقل فعالية لبناء الاستعلامات العشوائية. باختصار، للمطور الذي يتقن SQL، استخدام sqlc يشعر بشكل طبيعي وعالي الكفاءة. هناك القليل الجديد لتعلم، ويزيل التكرار في كود Go. للمطور الذي ليس معتادًا جدًا على SQL، قد تكون sqlc أبطأ في البداية (مقارنة بـ ORM يُنتج استعلامات تلقائيًا لـ CRUD بسيط)، لكن العديد من المطورين في Go يعتبرون sqlc ضروريًا لأنها تصل إلى نقطة مثالية: السيطرة اليدوية مع الأمان العالي وبدون تكاليف تشغيلية.

شعبية ودعم النظام البيئي

يمكن أن تؤثر ا adoption والدعم المجتمعي على خيارك - مكتبة شائعة تعني مساهمات مجتمعية أكثر، وصيانة أفضل، وموارد أكثر للتعلم من.

  • GORM: كأقدم وأكثر نضجًا بين الأربعة، فإن GORM لديه قاعدة مستخدمين أكبر بكثير والنظام البيئي. إنه حاليًا أحدث مكتبة ORM على GitHub (أكثر من 38 ألف نجمة) ويتم استخدامه في مشاريع إنتاج لا حصر لها. المُحافظون نشيطون، ويتم تحديث المشروع بانتظام (GORM v2 كان تجديدًا كبيرًا يحسن الأداء والبنية). فائدة كبيرة من شعبية GORM هي الكم الهائل من التمديدات والتكاملات. هناك إضافات رسمية وطرفية للأشياء مثل قواعد البيانات (مثلاً لـ PostgreSQL، MySQL، SQLite، SQL Server، ClickHouse)، متوفرة بشكل افتراضي. GORM أيضًا يدعم مجموعة واسعة من الحالات الاستخدامية: التحديثات، إنشاء المخطط تلقائيًا، الحذف الناعم، الحقول JSON (مع gorm.io/datatypes)، البحث الكامل النصي، إلخ، غالبًا من خلال الوظائف المدمجة أو الإضافات. أنتج المجتمع أدوات مختلفة مثل gormt (لإنشاء تعريفات الهيكل من قاعدة بيانات موجودة)، وعدد كبير من الدروس وال أمثلة. إذا واجهت مشكلة، فإن بحثًا سريعًا من المحتمل أن يجد قضية أو سؤالًا تم طرحه على Stack Overflow من قبل شخص آخر. ملخص النظام البيئي: GORM مدعوم بشكل ممتاز جدًا. إنه الخيار الافتراضي ORM للكثيرين، مما يعني أنك ستجده في الإطارات والقوالب. الجانب الآخر هو أن حجمه الكبير يمكن أن يجعله يبدو ثقيلًا، ولكن بالنسبة للكثيرين، فإن هذا التبادل هو أمر وارد لعمق المجتمع.

  • Ent: على الرغم من كونه أحدث (تم إصداره مفتوح المصدر حوالي عام 2019)، فإن Ent نما مجتمعًا قويًا. مع ~16k نجوم ودعم من قبل مؤسسة Linux، فإنه ليس مشروعًا هامشيًا. بدأت شركات كبيرة باستخدام Ent لفوائده المركزة على المخطط، والمُحافظون (في Ariga) يوفرون دعمًا تجاريًا، وهو مؤشر على الثقة في الاستخدامات الحرجة. النموذج البيئي حول Ent ينمو: هناك تمديدات Ent (ent/go) للأشياء مثل دمج OpenAPI/GraphQL، دمج gRPC، وحتى أداة تحويل SQL التي تعمل مع مخططات Ent. نظرًا لأن Ent يولد الكود، فإن بعض أنماط النظام البيئي تختلف - على سبيل المثال، إذا أردت دمج GraphQL، فقد تستخدم إنشاء الكود من Ent لإنتاج حلول GraphQL. موارد التعلم لـ Ent جيدة (التوثيق الرسمي ومشروع مثال يغطي تطبيقًا بسيطًا). منتديات المجتمع ونقاشات GitHub نشطة مع أسئلة حول تصميم المخططات والنصائح. من حيث دعم المجتمع، Ent بالتأكيد مر من مرحلة “المبادر المبكر” ويعتبر جاهزًا للإنتاج وموثوقًا. قد لا يكون لديه عدد من إجابات Stack Overflow مثل GORM بعد، لكنه يزداد بسرعة. شيء واحد يجب ملاحظته: نظرًا لأن Ent أكثر رأيًا (مثلاً، يريد أن يدير المخطط)، فمن المحتمل أنك ستستخدمه بمفرده وليس مع ORM آخر. لا يحتوي Ent على “إضافات” بنفس الطريقة التي يحتويها GORM، ولكن يمكنك كتابة قوالب مخصصة لتوسيع إنشاء الكود أو الربط في أحداث الدورة (Ent لديه دعم للربط/الوسيط للكlients المُنشئين). دعم المؤسسة يشير إلى دعم طويل الأمد، لذا فإن اختيار Ent هو خيار آمن إذا كان نموذجك يناسب احتياجاتك.

  • Bun: Bun (جزء من مجموعة Uptrace المفتوحة المصدر) يكتسب شعبية، خاصة بين أولئك الذين كانوا معجبين بمكتبة go-pg التي لم تعد مُدارة. مع ~4.3k نجوم، إنه أصغر مجتمع في هذه المقارنة، لكنه مشروع نشط جدًا. المُحافظ نشيط، ويضيف الميزات بسرعة. مجتمع Bun متحمس حول أداءه. ستجد مناقشات على منتديات Go وReddit من مطورين تبادلوا إلى Bun من أجل السرعة. ومع ذلك، نظرًا لأن قاعدة المستخدمين أصغر، فقد لا تجد إجابات على الأسئلة المتخصصة بسهولة - أحيانًا ستحتاج إلى قراءة الوثائق/المصدر أو طرح سؤال في منتدى GitHub أو Discord (Uptrace يوفر محادثة مجتمعية). النموذج البيئي للتمديدات أكثر تقييدًا: يحتوي Bun على مكتبة تحويله الخاصة، وتحميل مثال، وربط مع أدوات مراقبة Uptrace، لكنك لن تجد عددًا كبيرًا من الإضافات مثل GORM. ومع ذلك، فإن Bun متوافق مع استخدام sql.DB، لذا يمكنك مزيج وتقاطع - على سبيل المثال، استخدام github.com/jackc/pgx كمُحرك أسفل أو دمج مع حزم أخرى تنتظر *sql.DB. لا يقيدك Bun. من حيث الدعم، نظرًا لكونه أحدث، فإن الوثائق محدثة وال أمثلة حديثة (غالبًا تظهر استخدامها مع السياق وغيرها). الوثائق الرسمية تقارن Bun مباشرة مع GORM وEnt، وهو مفيد. إذا كانت حجم المجتمع مصدر قلق، فقد يكون أحد الاستراتيجيات اعتماد Bun لفوائده ولكن استخدامه بشكل نسبي ضحل (مثلاً، يمكنك تبديله لحل آخر إذا لزم الأمر نظرًا لأنه لا يفرض تجريدًا ثقيلًا). في أي حال، فإن مسار Bun صاعد، ويملأ فجوة معينة (ORM موجه نحو الأداء) مما يمنحه قوة استمرارية.

  • sqlc: sqlc شائع جدًا في مجتمع Go، كما يدل على ~15.9k نجوم وعدد كبير من المؤيدين خاصة في الدوائر التي تهتم بالأداء. غالبًا ما يُنصح به في المناقشات حول “تجنب ORMs” لأنه يحقق توازنًا جيدًا. الأداة مُدارة من قبل المساهمين (بما في ذلك المؤلف الأصلي، الذي نشيط في تحسينها). نظرًا لكونه أكثر من مكتبة وقت التشغيل، فإن النموذج البيئي يدور حول التكاملات: على سبيل المثال، يمكن لمحررات IDE أن يكون لديها تلوين لـ .sql الملفات وتشغيل sqlc generate كجزء من بناءك أو أنبوب CI الخاص بك. أنشأ المجتمع قوالب و أمثلة على كيفية تنظيم الكود مع sqlc (غالبًا ما يقترن بمحفظة تحويل مثل Flyway أو Golang-Migrate لتحديد إصدار المخطط، نظرًا لأن sqlc نفسه لا يدير المخطط). هناك قناة Slack/Discord رسمية لـ sqlc حيث يمكنك طرح الأسئلة، ومشاكل GitHub تميل إلى الحصول على انتباه. العديد من الأنماط الشائعة (مثل كيفية التعامل مع القيم القابلة للإلغاء أو الحقول JSON) موثقة في وثائق sqlc أو لها مقالات مجتمعية. شيء واحد يجب تسليط الضوء عليه: sqlc ليس محددًا لـ Go - يمكنه إنشاء كود في لغات أخرى (مثل TypeScript، Python). هذا يوسع مجتمعه خارج Go، ولكن في Go تحديدًا، فهو مُحترم على نطاق واسع. إذا اخترت sqlc، فأنت في جماعة جيدة: يستخدمه العديد من الشركات الناشئة والشركات الكبيرة (كما يدل على عروض المجتمع والرعاة المذكورة في المستودع). الاعتبار البيئي الرئيسي هو أن sqlc لا يوفر ميزات وقت التشغيل مثل ORM، لذا قد تحتاج إلى سحب مكتبات أخرى للقيام بأمور مثل المعاملات (على الرغم من أنك يمكن استخدام sql.Tx بسهولة مع sqlc) أو ربما مُغطاة DAL خفيفة. في الممارسة العملية، يستخدم معظم الناس sqlc مع المكتبة القياسية (للمعاملات، إلغاء السياق، إلخ، تستخدم أدوات database/sql مباشرة في كودك). هذا يعني أقل قيودًا على المورِّد - الابتعاد عن sqlc يعني فقط كتابة طبقة بياناتك الخاصة، وهو بنفس صعوبة كتابة SQL (الذي قمت به بالفعل). في المجموع، الدعم والمجتمع لـ sqlc قوي، مع العديد من الناس يوصون به كـ “مُستخدم ضروري” لمشاريع Go التي تتعامل مع قواعد بيانات SQL بسبب بساطته وموثوقيته.

مجموعة الميزات والقابلية للتوسيع

كل هذه الأدوات توفر مجموعة مختلفة من الميزات. هنا نقارن قدراتهم وكيفية توسعهم لحالات الاستخدام المتقدمة:

  • ميزات GORM: يهدف GORM إلى أن يكون ORM كامل الخدمات. قائمة ميزاته واسعة:
  • قواعد بيانات متعددة: دعم أولي لـ PostgreSQL، MySQL، SQLite، SQL Server، وغيرها. تغيير قواعد البيانات عادة ما يكون سهلًا مثل تغيير محرك الاتصال.
  • التحديثات: يمكنك تحديث مخططك تلقائيًا من نماذجك (db.AutoMigrate(&User{}) سيقوم بإنشاء أو تعديل جدول users). على الرغم من الراحة، احذر من استخدام التحديث التلقائي في الإنتاج - يستخدمه العديد من الأشخاص في التطوير ويقومون بتحديثات أكثر تحكمًا في الإنتاج.
  • العلاقات: علامات GORM (gorm:"foreignKey:...,references:...") تسمح لك بتعريف العلاقات.OneToOne، ManyToMany، إلخ. يمكنه التعامل مع جداول الربط لـ ManyToMany و لديه Preload لتحميل العلاقات بسرعة. يفتقر GORM إلى التحميل المبكر (أي، استعلامات منفصلة) عند الوصول إلى الحقول المرتبطة، ولكن يمكنك استخدام Preload أو Joins لتخصيص ذلك. الإصدارات الأحدث أيضًا لديها واجهة تعتمد على generics لاستعلامات الارتباط بسهولة.
  • المعاملات: لديه مُغطاة سهلة الاستخدام (db.Transaction(func(tx *gorm.DB) error { ... })) و حتى دعم للمعاملات المضمنة (نقاط الحفظ).
  • المحولات والردود: يمكنك تعريف وظائف مثل BeforeCreate، AfterUpdate على هيكل نماذجك، أو تسجيل ردود عالمية سيسجلها GORM في أحداث دورة الحياة الخاصة. هذا رائع لأشياء مثل تعيين تواريخ تلقائيًا أو سلوك الحذف الناعم.
  • التوسع: نظام الإضافات لـ GORM (gorm.Plugin interface) يسمح بتوسيع وظائفه. أمثلة: حزمة gorm-gen تولد وظائف استعلام آمنة من الناحية النوعية (إذا كنت تفضل فحص الاستعلامات في وقت التجميع)، أو إضافات مجتمعية لتسجيل الأحداث، متعدد المستأجرين، إلخ. يمكنك أيضًا الاعتماد على SQL الخام في أي وقت عبر db.Raw("SELECT ...", params).Scan(&result).
  • مزايا أخرى: يدعم GORM مفاتيح رئيسية مركبة، إدراج النماذج، الارتباطات متعددة الأنواع، وحتى مُحلل المخطط لاستخدام قواعد بيانات متعددة (لنسخ القراء، تجزئة، إلخ).

بشكل عام، GORM متوسع للغاية ومزود بميزات واسعة. تقريبًا أي ميزة ORM تريدها لديها آلية مدمجة أو نمط موثق في GORM. التكلفة لهذا الاتساع هي التعقيد والبعض من الصلابة (غالبًا ما تحتاج إلى الالتزام بطريقة GORM في التعامل). ولكن إذا كنت بحاجة إلى حل واحد، فإن GORM يوفره.

  • ميزات Ent: فلسفته مركزة على المخطط ككود. الميزات الرئيسية تشمل:
  • تعريف المخطط: يمكنك تعريف الحقول مع القيود (فريدة، قيم افتراضية، تعداد، إلخ)، والحواف (العلاقات) مع الكمية (OneToMany، إلخ). يستخدم Ent هذا لإنشاء الكود ويمكنه أيضًا إنشاء SQL لتحديثات المخطط لك (هناك مكون ent/migrate يمكنه إنتاج SQL بين مخططك الحالي والمخطط المرغوب فيه).
  • الأمان والتحقق من النوع: نظرًا لأن الحقول مُصنفة بقوة، لا يمكنك، مثلاً، تعيين حقل عدد صحيح إلى سلسلة. يسمح Ent أيضًا بتحديد أنواع حقول مخصصة (مثلاً، يمكنك دمجها مع sql.Scanner/driver.Valuer لحقول JSONB أو أنواع معقدة أخرى).
  • مُنشئ الاستعلام: واجهة الاستعلام المُنشئة من Ent تغطي معظم بنى SQL: يمكنك إجراء تحديدات، ترشيحات، ترتيب، حدود، تجميعات، مراجعات عبر الحواف، حتى استعلامات فرعية. إنها تعبيرية - مثلاً، يمكنك كتابة client.User.Query().WithOrders(func(q *ent.OrderQuery) { q.Limit(5) }).Where(user.StatusEQ(user.StatusActive)).All(ctx) للحصول على مستخدمين نشطين مع أول 5 طلبات لكل منهم، في مرة واحدة.
  • المعاملات: يدعم Ent المعاملات عبر إظهار نسخة معاملة من العميل (عبر tx, err := client.Tx(ctx) التي تنتج ent.Tx يمكن استخدامها لإجراء عمليات متعددة ثم التأكيد أو التراجع).
  • المحولات والمتوسطات: يسمح Ent بتسجيل محولات على العمليات الإنشائية/التحديثية/الحذفية - هذه تشبه المُعطلين حيث يمكنك، مثلاً، تعبئة حقل تلقائيًا أو فرض قواعد مخصصة. هناك أيضًا متوسطات للعميل لحظر العمليات (مفيد للتسجيل، الأدوات).
  • التوسع: على الرغم من أن Ent لا يحتوي على “إضافات” بالمعنى الحرفي، فإن إنشاء الكود مُصنَّف ويمكنك كتابة قوالب مخصصة إذا كنت بحاجة لتوسيع الكود المُنشئ. تم تنفيذ العديد من الميزات المتقدمة بهذه الطريقة: على سبيل المثال، دمج OpenTelemetry للتعقب في مكالمات قاعدة البيانات، أو إنشاء حلول GraphQL من مخطط Ent، إلخ. يسمح Ent أيضًا بدمج SQL الخام عند الحاجة عبر حزمة entsql أو من خلال الحصول على المحرك الأساسي. القدرة على إنشاء كود إضافي تعني أن الفرق يمكن استخدام Ent كأساس ووضع أنماطهم الخاصة فوقه إذا لزم الأمر.
  • دمج GraphQL/REST: نقطة بيع رئيسية لنهج Ent القائم على الرسم هو أنه يناسب جيدًا مع GraphQL - يمكن ترجمة مخطط Ent تقريبًا مباشرة إلى مخطط GraphQL. توجد أدوات لتسهيل الكثير من ذلك. هذا يمكن أن يكون فوزًا في الإنتاجية إذا كنت تبني خادم API.
  • تحسينات الأداء: على سبيل المثال، يمكن لـ Ent تحميل الحواف المرتبطة بشكل دفعة لتجنب مشاكل N+1 (لديه واجهة تحميل مبكر .With<EdgeName>()). سيستخدم JOINs أو استعلامات إضافية تحت الغطاء بطريقة محسنة. كما يمكن تمكين تخزين نتائج الاستعلام (في الذاكرة) لتجنب الوصول إلى قاعدة البيانات لاستعلامات متطابقة في فترة قصيرة.

باختصار، مجموعة ميزات Ent موجهة نحو المشاريع الكبيرة، القابلة للصيانة. قد تفتقر إلى بعض المزايا الجاهزة لـ GORM (مثلاً، تحديث المخطط التلقائي لـ GORM مقابل إنشاء ملفات تحويل لـ Ent - Ent يختار فصل هذه المخاوف)، لكنها تعوض ذلك ب أدوات تطوير قوية والأمان من النوع. التوسع في Ent هو حول إنشاء ما تحتاجه - إنه مرن جدًا إذا كنت مستعدًا للغوص في كيفية عمل entc (المحول).

  • ميزات Bun: يركز Bun على أن يكون أداة قوية لمن يعرف SQL. بعض الميزات والنقاط القابلة للتوسيع:
  • مُنشئ الاستعلام: في قلب Bun هو مُنشئ الاستعلام الناعم الذي يدعم معظم بنى SQL (SELECT مع مراجعات، CTEs، استعلامات فرعية، بالإضافة إلى INSERT، UPDATE مع دعم كتلة). يمكنك البدء في استعلام وتعديلها بعمق، حتى إدخال SQL الخام (.Where("some_condition (?)", value)).
  • ميزات محددة لـ PostgreSQL: يحتوي Bun على دعم مدمج لـ Postgres أنواع المصفوفة، JSON/JSONB (التوافق مع []<type> أو map[string]interface{} أو أنواع مخصصة)، ويدعم مسحًا إلى أنواع مركبة. على سبيل المثال، إذا كان لديك عمود JSONB، يمكنك خرائطه إلى هيكل Go مع علامات json:، وBun سيتعامل مع التسلسل لك. هذا قوة مقارنة مع بعض ORMs التي تتعامل مع JSONB كopaque.
  • العلاقات/التحميل المبكر: كما أظهرنا سابقًا، يسمح Bun بتعريف علامات الهيكل للعلاقات ويمكنك استخدام .Relation("FieldName") في الاستعلامات للربط والتحميل الكيانات المرتبطة. الافتراضي، .Relation يستخدم LEFT JOIN (يمكنك محاكاة INNER JOINs أو أنواع أخرى بإضافة الظروف). هذا يمنحك تحكمًا دقيقًا في كيفية استرجاع البيانات المرتبطة (في استعلام واحد مقابل عدة استعلامات). إذا كنت تفضل الاستعلامات اليدوية، يمكنك دائمًا كتابة JOIN في مُنشئ SQL لـ Bun مباشرة. على عكس GORM، لن يحمّل Bun العلاقات تلقائيًا دون طلبك، مما يتجنب مشاكل N+1 غير المرغوب فيها.
  • التحديثات: يوفر Bun أداة تحويل منفصلة (في github.com/uptrace/bun/migrate). يتم تعريف التحديثات في Go (أو سلسلة SQL) وترقيمها. إنه ليس بنفس السحر التلقائي لـ GORM auto-migrate، لكنه قوي ويتكامل مع المكتبة بشكل جيد. هذا رائع للحفاظ على تغييرات المخطط صريحة.
  • التوسع: يتم بناء Bun على واجهات مشابهة لـ database/sql - حتى يلفّ *sql.DB. يمكنك إذن استخدام أي أداة منخفضة المستوى معه (مثلاً، يمكنك تنفيذ استعلام *pgx.Conn إذا لزم الأمر وستظل تمسح إلى نماذج Bun). يسمح Bun بمسح القيم المخصصة، لذا إذا كان لديك نوع معقد تريد تخزينه، يمكنك تنفيذ sql.Scanner/driver.Valuer وBun سيستخدمه. بالنسبة لتوسيع وظائف Bun، نظرًا لكونه نسبيًا بسيطًا، فإن العديد من الناس فقط يكتبون وظائف مساعدة إضافية على واجهة API لـ Bun في مشاريعهم بدلاً من إضافات منفصلة. تتطور المكتبة نفسها، لذا يتم إضافة ميزات جديدة (مثل مساعدين إضافيين للاستعلامات أو التكاملات) من قبل المُحافظين.
  • ميزات أخرى: يدعم Bun إلغاء السياق (كل الاستعلامات تقبل ctx)، لديه خزان اتصال عبر database/sql (قابل للتكوين هناك)، ويدعم تخزين الاستعلامات المُعدة (من خلال المحرك إذا كنت تستخدم pgx). هناك أيضًا ميزة لطيفة حيث يمكن لـ Bun مسح إلى مؤشرات الهيكل أو شرائح بسيطة بسهولة (مثلاً، اختيار عمود واحد إلى []string مباشرة). هذه الميزات الصغيرة هي ما يجعل Bun ممتعًا لمن يكرهون كود التسجيل المتكرر.

باختصار، مجموعة ميزات Bun تغطي 90% من احتياجات ORM ولكن مع التركيز على الشفافية والأداء. قد لا يكون لديه كل الميزات (مثلاً، لا يفعل تحديثات المخطط التلقائية أو نمط النسخة النشطة)، لكنه يوفر المكونات الأساسية لتنفيذ ما تحتاجه. نظرًا لكونه صغيرًا، توقع أن تستمر ميزاته في النمو، موجهة بحالات الاستخدام الواقعية (المُحافظون غالبًا يضيفون ميزات حسب طلب المستخدمين).

  • ميزات sqlc: “ميزات” sqlc مختلفة تمامًا نظرًا لكونه مُحولًا:
  • دعم SQL الكامل: أكبر ميزة هي ببساطة أنك تستخدم ميزات PostgreSQL الخاصة به مباشرة. إذا دعم Postgres، يمكنك استخدامها في sqlc. هذا يشمل CTEs، وظائف النافذة، مشغلي JSON، استعلامات مساحية (PostGIS)، إلخ. لا حاجة للمكتبة نفسها لتنفيذ أي شيء خاص لاستخدام هذه الميزات.
  • مappings الأنواع: يفهم sqlc بشكل ذكي كيفية تعيين أنواع SQL إلى أنواع Go. يتعامل مع الأنواع القياسية ويسمح لك بتكوين أو توسيع المappings للأنواع المخصصة أو التعدادات. على سبيل المثال، يمكن أن يخريطة Postgres UUID إلى نوع github.com/google/uuid إذا أردت، أو يمكن أن يخريطة نوع مجال إلى نوع Go الأساسي.
  • التعامل مع القيم الفارغة: يمكنه إنشاء sql.NullString أو مؤشرات حسب تفضيلك للعمود القابل للإلغاء، بحيث لا تحتاج إلى القتال مع مسح القيم الفارغة.
  • العمليات بالجملة: على الرغم من أن sqlc نفسه لا يوفر واجهة عالية المستوى للعمليات بالجملة، يمكنك بالتأكيد كتابة استعلام إدراج كتلة SQL وإنشاء كود له. أو استدعاء إجراء مخزن - هذه هي ميزة أخرى: نظرًا لكونه SQL، يمكنك الاستفادة من إجراءات مخزنة أو وظائف قاعدة البيانات، وجعل sqlc ينشئ مغطاة Go.
  • استعلامات متعددة الجمل: يمكنك وضع عدة جمل SQL في استعلام واحد مسمى، وsqlc سيقوم بتشغيلها في معاملة (إذا دعم المحرك ذلك)، ويعود نتائج الاستعلام الأخير أو ما تحدد. هذا طريقة لإجراء شيء مثل “إنشاء ثم اختيار” في مكالمة واحدة.
  • التوسع: كمُحول، يظهر التوسع على شكل إضافات لغات جديدة أو مساهمات مجتمعية لدعم بنى SQL جديدة. على سبيل المثال، إذا ظهر نوع بيانات جديد في PostgreSQL، يمكن تحديث sqlc لدعم خريطة له. في تطبيقك، يمكنك توسيع حول sqlc من خلال كتابة وظائف مغطاة. نظرًا لأن الكود المُنشئ تحت سيطرتك، يمكنك تعديله - على الرغم من أنك عادةً لن تفعل ذلك، ستقوم بتعديل SQL وإعادة إنشاء. إذا لزم الأمر، يمكنك دائمًا مزيج مكالمات database/sql الخام مع sqlc في تطبيقك (لا يوجد تعارض، نظرًا لأن sqlc فقط ينتج بعض الكود Go).
  • الاعتماديات الزمنية المعدة: الكود الذي ينتج sqlc يعتمد عادة فقط على database/sql القياسية (ود라이فر محدد مثل pgx). لا يوجد مكتبة زمنية ثقيلة؛ مجرد بعض الأنواع المساعدة. هذا يعني عدم وجود تحميل إضافي من جانب المكتبة في الإنتاج - كل العمل يحدث في وقت التجميع. هذا يعني أيضًا أنك لن تحصل على ميزات مثل مخزن الكائنات أو خريطة الهوية (كما هو الحال في بعض ORMs) - إذا كنت بحاجة إلى التخزين، فسيتم تنفيذه في طبقة خدمتك أو استخدام مكتبة منفصلة.

بشكل عملي، “ميزة” sqlc هي أن تقلل الفجوة بين قاعدة بياناتك SQL و كودك Go دون إضافة شيء إضافي بينهما. إنه أداة متخصصة - لا يفعل التحديثات، ولا يتعقب حالة الكائن، إلخ، تصميمًا. هذه المخاوف تتركها ل أدوات أخرى أو للمطور. هذا مثير إذا كنت تبحث عن طبقة بيانات رقيقة، ولكن إذا كنت تبحث عن حل واحد يتعامل مع كل شيء من المخطط إلى الاستعلامات إلى العلاقات في الكود، فإن sqlc وحده لا يكون - ستقوم بتوصيله مع أنماط أو أدوات أخرى.

لإعادة تلخيص هذا القسم، GORM هو قوة الميزات وواضح الخيار إذا كنت بحاجة إلى ORM ناضج وقابل للتوسيع يمكن تكييفه عبر الإضافات. Ent يقدم نظرة حديثة وآمنة من حيث النوع على الميزات، مع أولوية للدقة والصيانة (مع أشياء مثل إنشاء الكود، المحولات، والتكاملات مع طبقات API). Bun يوفر الأساسيات ويستند إلى معرفة SQL، مما يجعله سهل التوسع من خلال كتابة المزيد من SQL أو مغطاة بسيطة. sqlc يقلل الميزات إلى المعدن الصلب - إنه تقريبًا بنفس ثراء SQL، لكن أي شيء خارج ذلك (التخزين، إلخ) يعتمد عليك لطبقة إضافية.

مثال على الكود: عمليات CRUD في كل ORM

لا شيء يوضح الفروقات بشكل أفضل من رؤية كيفية التعامل مع عمليات CRUD الأساسية لنموذج بسيط باستخدام كل أداة. لنفترض أن لدينا نموذجًا يُسمى User مع الحقول ID و Name و Email. أدناه نعرض قطعًا من الكود المقابلة لعمليات إنشاء، قراءة، تحديث، وحذف مستخدم في كل مكتبة، باستخدام PostgreSQL كقاعدة بيانات.

ملاحظة: في جميع الحالات، نفترض أن لدينا اتصالًا بالقاعدة البيانات (مثلاً db أو client) تم إعداده مسبقًا، ونترك معالجة الأخطاء جانبًا من أجل البساطة. الهدف هو مقارنة واجهة كل أسلوب ووضوحها.

GORM (أسلوب Active Record)

    // تعريف النموذج
    type User struct {
        ID    uint   `gorm:"primaryKey"`
        Name  string
        Email string
    }

    // إنشاء مستخدم جديد
    user := User{Name: "Alice", Email: "alice@example.com"}
    db.Create(&user)                      // INSERT INTO users (name,email) VALUES ('Alice','alice@example.com')

    // القراءة (البحث حسب المفتاح الرئيسي)
    var u User
    db.First(&u, user.ID)                // SELECT * FROM users WHERE id = X LIMIT 1

    // التحديث (حقل واحد)
    db.Model(&u).Update("Email", "alice_new@example.com")
    // (يُنتج: UPDATE users SET email='alice_new@example.com' WHERE id = X)

    // الحذف 
    db.Delete(&User{}, u.ID)             // DELETE FROM users WHERE id = X

Ent (الcodegen، واجهة API سلسة)

    // (يتم تعريف نموذج Ent في مكان آخر ويتم إنشاء الكود تلقائيًا. نفترض أن العميل هو ent.Client)

    // إنشاء مستخدم جديد
    u, err := client.User.
        Create().
        SetName("Alice").
        SetEmail("alice@example.com").
        Save(ctx)
    // SQL: INSERT INTO users (name, email) VALUES ('Alice','alice@example.com') RETURNING id, name, email

    // القراءة (بحسب ID)
    u2, err := client.User.Get(ctx, u.ID)
    // SQL: SELECT * FROM users WHERE id = X

    // التحديث (حقل واحد)
    u3, err := client.User.
        UpdateOneID(u.ID).
        SetEmail("alice_new@example.com").
        Save(ctx)
    // SQL: UPDATE users SET email='alice_new@example.com' WHERE id = X RETURNING id, name, email

    // الحذف 
    err = client.User.DeleteOneID(u.ID).Exec(ctx)
    // SQL: DELETE FROM users WHERE id = X

Bun

    // تعريف النموذج
    type User struct {
        bun.BaseModel `bun:"table:users"` 
        ID   int64    `bun:",pk,autoincrement"`
        Name string   `bun:",notnull"`
        Email string  `bun:",unique,notnull"`
    }

    // إنشاء مستخدم جديد
    user := &User{Name: "Alice", Email: "alice@example.com"}
    _, err := db.NewInsert().Model(user).Exec(ctx)
    // INSERT INTO users (name, email) VALUES ('Alice','alice@example.com')

    // القراءة (البحث حسب ID)
    var u User
    err = db.NewSelect().Model(&u).
          Where("id = ?", user.ID).
          Scan(ctx)
    // SELECT * FROM users WHERE id = X

    // التحديث (حقل واحد)
    _, err = db.NewUpdate().Model(&u).
          Set("email = ?", "alice_new@example.com").
          Where("id = ?", u.ID).
          Exec(ctx)
    // UPDATE users SET email='alice_new@example.com' WHERE id = X

    // الحذف 
    _, err = db.NewDelete().Model((*User)(nil)).
          Where("id = ?", u.ID).
          Exec(ctx)
    // DELETE FROM users WHERE id = X

sqlc


    // نفترض أننا كتبتنا SQL في ملفات وتم إنشاء هيكل Queries مع وظائف.
    queries := New(db)  // New يأخذ *sql.DB (أو pgx.Conn) ويُرجع Queries المُولدة

    // إنشاء مستخدم جديد (الوظيفة المُولدة تنفذ INSERT وتنسق النتيجة)
    newUser, err := queries.CreateUser(ctx, "Alice", "alice@example.com")
    // SQL في queries.sql -> INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email

    // القراءة (بحسب ID)
    user, err := queries.GetUser(ctx, newUser.ID)
    // SQL -> SELECT id, name, email FROM users WHERE id = $1

    // التحديث (البريد الإلكتروني حسب ID)
    updatedUser, err := queries.UpdateUserEmail(ctx, newUser.ID, "alice_new@example.com")
    // SQL -> UPDATE users SET email=$2 WHERE id = $1 RETURNING id, name, email

    // الحذف 
    err = queries.DeleteUser(ctx, newUser.ID)
    // SQL -> DELETE FROM users WHERE id = $1

كما يوضح الكود أعلاه، فإن كل أسلوب له شعور مختلف:

  • GORM يستخدم وظائف النموذج وسلسلة الواجهة السلسة على db.Model(&obj) أو مباشرة على الكائن db. يقوم بتعبئة النموذج بالقيم المُعادَة (مثلاً بعد Create، يتم تعيين user.ID). كما يخفي تفاصيل SQL تصميمًا – عادةً ما لا ترى الاستعلام إلا إذا كنت تُعديله.

  • Ent يستخدم واجهة API سلسة مُولدة. لاحظ كيف تفصل الوظائف مثل Create().SetX().Save(ctx) أو UpdateOneID(id).SetX().Save(ctx) بين مراحل البناء والتنفيذ. يُرجع Ent كائنات من نوع ent.Type (التي تتوافق مع الصفوف) و الأخطاء، مشابهةً لما يُعاد من استعلام SQL، إما النتائج أو الأخطاء.

  • Bun يتطلب تحديدًا أكثر صراحة (مثلاً استخدام Set("email = ?", ...) للتحديث)، وهو يشبه كتابة SQL تمامًا ولكن باستخدام لغة Go. بعد الإدراج، لا يتم تعبئة الكائن user تلقائيًا بالـ ID الجديد إلا إذا أضفتَ بناءً على RETURNING (Bun يدعم .Returning() إذا لزم الأمر). يبسط المثال أعلاه الأمور.

  • sqlc يشبه استدعاء أي وظيفة Go. نقوم باستدعاء queries.CreateUser، إلخ، وتحت الغطاء، هذه هي التي تنفذ البيانات المُعدة مسبقًا. يتم كتابة SQL في ملفات خارجية، لذا بينما لا تراها في الكود Go، فإنك تحصل على سيطرة كاملة عليها. تُعاد الكائنات (مثلاً newUser) كهيكل Go عادي مُولدة من sqlc لتمثيل البيانات.

يمكن ملاحظة اختلافات في الوضوح (GORM مختصر جدًا، بينما Bun و sqlc يتطلبان كتابة أكثر في الكود أو SQL) و اختلافات في الأسلوب (Ent و GORM يوفران تجريدات مستوى أعلى، بينما Bun و sqlc أقرب إلى الاستعلامات الأصلية). حسب تفضيلاتك، قد تفضل الوضوح على البساطة أو العكس.


TL;DR

اختيار “الORM أو مكتبة قاعدة البيانات الصحيحة” في Go يعتمد على احتياجات تطبيقك وتفضيلات فريقك:

  • GORM هو خيار ممتاز إذا كنت ترغب في ORM مُجرب يتعامل مع الكثير من الأمور لك. يبرز في تطوير تطبيقات CRUD بسرعة حيث تكون الراحة ومجموعة الميزات الغنية أكثر أهمية من استغلال كل قطرة من الأداء. دعم المجتمع والتوثيق ممتاز، مما يمكنه من تسوية الحواف الخشنة لمنحنى التعلم. كن حذرًا من استخدام ميزاته بشكل مناسب (مثلاً استخدم Preload أو Joins لتجنب أخطاء التحميل المتأخر)، وانتظر بعض التكلفة الإضافية. في المقابل، تحصل على الإنتاجية وحلًا واحدًا للعديد من المشكلات. إذا كنت بحاجة إلى أشياء مثل دعم قواعد بيانات متعددة أو نظام إضافات واسع النطاق، فإن GORM يغطيك.

  • Ent يجذب أولئك الذين يفضلون الأمان النوعي، والوضوح، والصيانة. إنه مناسب جدًا للمشاريع الكبيرة حيث تحدث تغييرات في المخطط بشكل متكرر، وتريد أن يكون المترجم معك لالتقاط الأخطاء. قد يتطلب Ent تصميمًا مسبقًا أكثر (تعريف المخططات، تشغيل التوليد)، لكنه يدفع ثمنًا عند نمو المشروع – يبقى الكود قويًا وسهل التحويل. من حيث الأداء، يمكنه التعامل مع الأحمال الثقيلة والاستعلامات المعقدة بكفاءة، غالبًا أفضل من ORMs من نوع Active Record، بفضل توليد SQL المحسّن و التخزين المؤقت. Ent أقل “plug-and-play” للكتابات السريعة، لكنه يوفر أساسًا قويًا وقابلًا للتوسع للخدمات طويلة الأمد. نهجه الحديث (والتطوير النشط) يجعله خيارًا مُستقبليًا.

  • Bun مثالي للمطورين الذين يقولون “أنا أعرف SQL وأريد مساعدة خفيفة فقط”. يتنازل عن بعض السحر ليعطيك التحكم و السرعة. إذا كنت تبني خدمة حساسة للأداء ولا تخاف من أن تكون صريحًا في كود الوصول إلى البيانات، فإن Bun خيار مقنع. إنه أيضًا خيار مناسب إذا كنت تنتقل من database/sql+sqlx وتريد إضافة بعض الهيكل دون التضحية بكفاءة كبيرة. التبادل هو مجتمع أصغر وعدد أقل من التجردات العالية – ستعمل قليلاً أكثر يدويًا. لكن كما تقول وثائق Bun، لا يعيقك. استخدم Bun عندما ترغب في ORM يشعر كأنك تستخدم SQL مباشرة، خاصةً للمشاريع التي تركز على PostgreSQL حيث يمكنك الاستفادة من ميزات قاعدة البيانات المحددة.

  • sqlc ينتمي إلى فئة منفصلة. إنه مثالي للفريق Go الذي يقول “لا نريد ORM، نريد ضمانات في وقت التجميع لـ SQL”. إذا كنت تمتلك مهارات قوية في SQL وتفضل إدارة الاستعلامات والمخطط في SQL (ربما لديك DBAs أو تحب صياغة SQL فعالة)، فإن sqlc سيزيد من إنتاجيتك وثقتك. الأداء تقريبًا مثالي، لأن لا شيء يقف بين استعلامك وقاعدة البيانات. السبب الوحيد الذي لا يجب استخدام sqlc هو إذا كنت تكره حقًا كتابة SQL أو إذا كانت استعلاماتك ديناميكية جدًا بحيث تصبح كتابة العديد من المتغيرات مرهقة. حتى في هذه الحالة، قد تستخدم sqlc لمعظم الاستعلامات الثابتة وتعالج القليل من الحالات الديناميكية بطريقة أخرى. sqlc يلعب أيضًا جيدًا مع الآخرين – لا يمنع استخدام ORM في أجزاء من مشروعك (بعض المشاريع تستخدم GORM للأشياء البسيطة و sqlc للمسارات الحرجة، مثلاً). في المختصر، اختر sqlc إذا كنت تفضل الوضوح، عدم وجود تكلفة إضافية، وSQL آمن من الناحية النوعية – إنه أداة قوية لمساعدتك.

أخيرًا، من المهم ملاحظة أن هذه الأدوات ليست مُستبعدة في النظام البيئي. لكل منها مزايا وعيوب، وفي روح Go العملية، تقيّم فرق العمل التبادل على أساس كل حالة على حدة. من الشائع أن تبدأ بـ ORM مثل GORM أو Ent من أجل سرعة التطوير، ثم تستخدم sqlc أو Bun لمسارات محددة تحتاج إلى أقصى أداء. جميع الحلول الأربعة مُحافظة ومُستخدمه على نطاق واسع، لذا لا يوجد خيار “خاطئ” بشكل عام – إنه يتعلق بالخيار الصحيح لسياقك. أمل أن هذه المقارنة أعطتك صورة أوضح لكيفية ترتيب GORM، Ent، Bun، و sqlc، ويساعدك في اتخاذ قرار مُدروس لمشروع Go التالي.

روابط مفيدة