1. تثبيت أداة ent

لتثبيت أداة إنت لتوليد الكود، يجب عليك اتباع هذه الخطوات:

أولاً، تأكد من أن نظامك قد قام بتثبيت بيئة لغة البرمجة Go. بعد ذلك، قم بتشغيل الأمر التالي للحصول على أداة ent:

go get -d entgo.io/ent/cmd/ent

سيقوم هذا الأمر بتنزيل الكود الخاص بأداة ent، ولكن لن يتم تجميعها وتثبيتها فوراً. إذا أردت تثبيت ent في دليل $GOPATH/bin حتى تتمكن من استخدامه في أي مكان، يجب عليك أيضاً تنفيذ الأمر التالي:

go install entgo.io/ent/cmd/ent

عندما يتم الانتهاء من التثبيت، يمكنك التحقق مما إذا كانت أداة ent قد تم تثبيتها بشكل صحيح وعرض الأوامر المتاحة والتعليمات عن طريق تشغيل ent -h.

2. تهيئة النموذج

2.1 تهيئة القالب باستخدام ent init

إنشاء ملف نموذج جديد هو الخطوة الأولى لبدء استخدام ent لتوليد الكود. يمكنك تهيئة قالب النموذج عن طريق تنفيذ الأمر التالي:

go run -mod=mod entgo.io/ent/cmd/ent new User Pet

سيقوم هذا الأمر بإنشاء ملفي نموذج جديدين: user.go و pet.go، ووضعهما في دليل ent/schema. إذا لم يكن دليل ent موجودًا، فإن هذا الأمر سيقوم أيضاً بإنشائه تلقائياً.

تشغيل أمر ent init في الدليل الرئيسي للمشروع هو ممارسة جيدة، لأنه يساهم في الحفاظ على هيكل ووضوح دليل المشروع.

2.2 هيكل ملف النموذج

في دليل ent/schema، يتوافق كل نموذج مع ملف مصدر لغة Go. ملفات النموذج هي حيث تعرف نموذج قاعدة البيانات، بما في ذلك الحقول والروابط (العلاقات).

على سبيل المثال، في ملف user.go، قد تقوم بتعريف نموذج مستخدم، بما في ذلك الحقول مثل اسم المستخدم والعمر، وتعريف العلاقة بين المستخدمين والحيوانات الأليفة. بالمثل، في ملف pet.go، ستقوم بتعريف نموذج الحيوان الأليف وحقوله ذات الصلة، مثل اسم الحيوان الأليف، النوع، والعلاقة بين الحيوانات الأليفة والمستخدمين.

سيتم استخدام هذه الملفات في نهاية المطاف من قبل أداة ent لتوليد الكود الخاص بـ Go المقابل، بما في ذلك الكود الخاص بالعميل لعمليات قاعدة البيانات وعمليات CRUD (إنشاء، قراءة، تحديث، حذف).

// ent/schema/user.go
package schema

import (
    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

// User يعرف النموذج الخاص بكيان المستخدم.
type User struct {
    ent.Schema
}

// يُستخدم الأسلوب Fields لتعريف حقول الكيان.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").Unique(),
        field.Int("age").Positive(),
    }
}

// يتم استخدام الأسلوب Edges لتعريف الروابط للكيان.
func (User) Edges() []ent.Edge {
    // سيتم شرح العلاقات بتفصيل أكثر في القسم القادم.
}

تستخدم ملفات نموذج ent أنواع اللغة Go والوظائف لتعريف هيكل نموذج قاعدة البيانات، بما في ذلك الحقول والعلاقات بين النماذج، واستخدام واجهة برمجة التطبيقات المقدمة من إطار عمل ent لتعريف هذه الهياكل. تجعل هذه الطريقة ent سهلة التفاعل وسهلة التوسيع، وفي نفس الوقت تستفيد من الكتابة القوية في لغة Go.

3.1 تشغيل توليد الكود

تشغيل ent generate لتوليد الكود هو خطوة حاسمة في إطار عمل ent. من خلال هذا الأمر، ستقوم ent بتوليد ملفات الكود الخاصة بـ Go المقابل بناءً على النماذج المعرفة، مما يسهل العمل التطويري اللاحق. الأمر المستخدم لتنفيذ توليد الكود بسيط:

go generate ./ent

يجب تشغيل الأمر أعلاه في الدليل الرئيسي للمشروع. عند استدعاء go generate، سيقوم بالبحث عن جميع ملفات Go التي تحتوي على تعليقات محددة وتنفيذ الأوامر المحددة في التعليقات. في مثالنا، يحدد هذا الأمر مولد الكود لـ ent.

تأكد من أن تكون تهيئة نموذج وإضافة الحقول اللازمة قد تمت قبل التنفيذ. فقط بعد ذلك سيتضمن الكود المولد جميع الأجزاء اللازمة.

3.2 فهم أصول الشفرة المولدة

يحتوي أصول الشفرة المولدة على عدة مكونات، كل منها له وظائف مختلفة:

  • العناصر النمطية والمعاملات (Client and Tx objects): تُستخدم للتفاعل مع رسم البيانات. يوفّر العنصر النمطي (Client) طرقًا لإنشاء معاملات (Tx) أو تنفيذ العمليات المباشرة لقاعدة البيانات.

  • منشئات CRUD (CRUD builders): لكل نوع نمطي، يقوم بإنشاء منشئات (builders) للإنشاء والقراءة والتحديث والحذف، مما يبسط منطق العمليات الخاص بالكيان المقابل.

  • كائن الكيان (Go struct): يولّد الكيانات (structs) ذات الصلة بـ Go لكل نوع في النمط، مرتبطة بجداول في قاعدة البيانات.

  • حزمة الثوابت والمعاملات (Constants and predicates package): تحتوي على الثوابت والمعاملات للتفاعل مع البنّائي.

  • حزمة الهجرة (Migrate package): حزمة لهجرة قاعدة البيانات، مناسبة لللهجات الخاصة بـ SQL.

  • حزمة الخطاف (Hook package): توفّر وظيفة إضافة وسيطات التغيير، ممّا يسمح بتنفيذ منطق مخصّص قبل أو بعد إنشاء أو تحديث أو حذف الكيانات.

من خلال الفحص الشامل للشفرة المولدة، يمكنك الحصول على فهم أعمق حول كيفية أتمتة 'ent' لشفرة الوصول إلى البيانات للنماذج الخاصة بك.

4. خيارات توليد الشفرة

يدعم أمر ent generate مجموعة متنوعة من الخيارات لتخصيص عملية توليد الشفرة. يمكنك الاستعلام عن جميع الخيارات المدعومة لعملية التوليد من خلال الأمر التالي:

ent generate -h

فيما يلي بعض الخيارات المألوفة المستخدمة في سطر الأوامر:

  • --feature strings: يوسّع عملية توليد الشفرة، مضيفًا وظائف إضافية.
  • --header string: يستبدل ملف رأس توليد الشفرة.
  • --storage string: يحدد سائق التخزين المدعوم في عملية توليد الشفرة، مع الافتراضي "sql".
  • --target string: يحدد الدليل المستهدف لتوليد الشفرة.
  • --template strings: ينفذ قوالب Go إضافية. يدعم ملفًا ودليلاً ومسارًا واسعًا، على سبيل المثال: --template file="path/to/file.tmpl".

تتيح للمطورين هذه الخيارات تخصيص عملية توليد الشفرة وفقًا لاحتياجات وتفضيلات مختلفة.

5. تكوين خيارات التخزين

يدعم 'ent' توليد أصول الشفرة لكلا لهجات SQL وGremlin، حيث يكون الافتراضي للهجة SQL. إذا كان المشروع يحتاج إلى الاتصال بقاعدة بيانات Gremlin، فيجب تكوين الهجة المقابلة لقاعدة البيانات. يُظهر ما يلي كيفية تحديد خيارات التخزين:

ent generate --storage gremlin ./ent/schema

يوجّه الأمر أعلاه 'ent' لاستخدام الهجة Gremlin عند توليد الشفرة. يضمن ذلك أن تكون الأصول المولدة مصممة لمتطلبات قاعدة بيانات Gremlin المحددة، وذلك لضمان التوافق مع قاعدة بيانات الرسم البياني المحددة.

6. الاستخدام المتقدم: حزمة entc

6.1 استخدام entc كحزمة في المشروع

يُعتبر entc الحزمة الأساسية المستخدمة لتوليد الشفرة في إطار 'ent'. بالإضافة إلى أداة سطر الأوامر، يُمكن دمج entc أيضًا في المشروع كحزمة، مما يسمح للمطورين بالتحكم وتخصيص عملية توليد الشفرة داخل الشفرة نفسها.

لكي يتم استخدام 'entc' كحزمة في المشروع، يجب عليك إنشاء ملف يسمى entc.go وإضافة المحتوى التالي إليه:

// +build ignore

package main

import (
    "log"
    "entgo.io/ent/entc"
    "entgo.io/ent/entc/gen"
)

func main() {
    if err := entc.Generate("./schema", &gen.Config{}); err != nil {
        log.Fatal("running ent codegen:", err)
    }
}

عند استخدام هذا النهج، يُمكنك التعديل على هيكل gen.Config داخل الدالة main لتطبيق خيارات تكوين مختلفة. من خلال استدعاء دالة entc.Generate حسب الحاجة، يُمكنك التحكم بشكل مرن في عملية توليد الشفرة.

6.2 التكوين المفصل لـ entc

entc يوفر خيارات تكوين شاملة، مما يسمح للمطورين بتخصيص الشيفرة المولّدة. على سبيل المثال، يمكن تكوين خطاطيف مخصصة لتفقّد أو تعديل الشيفرة المولّدة، ويمكن حقن الاعتماديات الخارجية باستخدام حقن الاعتماديات.

المثال التالي يوضح كيفية توفير خطاطيف مخصصة لدالة entc.Generate:

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            HookFunction,
        },
    })
    if err != nil {
        log.Fatalf("تشغيل توليد شيفرة ent: %v", err)
    }
}

// HookFunction هي دالة خطاف مخصصة
func HookFunction(next gen.Generator) gen.Generator {
    return gen.GenerateFunc(func(g *gen.Graph) error {
        // يمكن التعامل مع الوضع البياني الذي يمثله g هنا
        // على سبيل المثال، التحقق من وجود الحقول أو تعديل الهيكل
        return next.Generate(g)
    })
}

بالإضافة إلى ذلك، يمكن إضافة الاعتماديات الخارجية باستخدام entc.Dependency:

func main() {
    opts := []entc.Option{
        entc.Dependency(
            entc.DependencyType(&http.Client{}),
        ),
        entc.Dependency(
            entc.DependencyName("Writer"),
            entc.DependencyTypeInfo(&field.TypeInfo{
                Ident:   "io.Writer",
                PkgPath: "io",
            }),
        ),
    }
    if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
        log.Fatalf("تشغيل توليد شيفرة ent: %v", err)
    }
}

في هذا المثال، نقوم بحقن http.Client و io.Writer كاعتماديات إضافية في الكائنات المولّدة.

7. الإخراج لوصف المخطط

في إطار ent، يمكن استخدام أمر ent describe لإخراج وصف للمخطط في شكل رسم بياني. يمكن لهذا المخرج مساعدة المطورين في فهم الكيانات والعلاقات الموجودة بسرعة.

نفّذ الأمر التالي للحصول على وصف للمخطط:

go run -mod=mod entgo.io/ent/cmd/ent describe ./ent/schema

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

User:
    +-------+---------+--------+-----------+ ...
    | Field |  Type   | Unique | Optional  | ...
    +-------+---------+--------+-----------+ ...
    | id    | int     | false  | false     | ...
    | name  | string  | true   | false     | ...
    +-------+---------+--------+-----------+ ...
    +-------+--------+---------+-----------+ ...
    | Edge  |  Type  | Inverse | Relation  | ...
    +-------+--------+---------+-----------+ ...
    | pets  | Pet    | false   | O2M       | ...
    +-------+--------+---------+-----------+ ...

8. خطافات توليد الشيفرة

8.1 مفهوم الخطافات

الخطافات هي دوال وسيطة يمكن إدراجها في عملية توليد الشيفرة ent، مما يسمح بإدراج منطق مخصص قبل وبعد توليد الشيفرة. يمكن استخدام الخطافات لتلاعب بشجرة البنية النحوية المجردة (AST) للشيفرة المولّدة، أو إجراء التحقق، أو إضافة مقتطفات شيفرة إضافية.

8.2 مثال على استخدام الخطافات

إليك مثال على استخدام خطاف لضمان أن جميع الحقول يحتوي على وسم هيكل معين (مثل json):

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            EnsureStructTag("json"),
        },
    })
    if err != nil {
        log.Fatalf("تشغيل توليد شيفرة ent: %v", err)
    }
}

// EnsureStructTag يضمن أن جميع الحقول في الرسم يحتوي على وسم هيكل معين
func EnsureStructTag(name string) gen.Hook {
    return func(next gen.Generator) gen.Generator {
        return gen.GenerateFunc(func(g *gen.Graph) error {
            for _, node := range g.Nodes {
                for _, field := range node.Fields {
                    tag := reflect.StructTag(field.StructTag)
                    if _, ok := tag.Lookup(name); !ok {
                        return fmt.Errorf("وسم الهيكل %q مفقود للحقل %s.%s", name, node.Name, field.Name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

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