1. نصب ابزار ent

برای نصب ابزار تولید کد ent، باید این مراحل را دنبال کنید:

ابتدا مطمئن شوید که محیط Go برنامه نویسی بر روی سیستم شما نصب شده است. سپس دستور زیر را اجرا کنید تا ابزار ent را دریافت کنید:

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

این دستور کد ent را دانلود می‌کند، اما آن را ترجمه و نصب نمی‌کند. اگر می‌خواهید ent را در دایرکتوری $GOPATH/bin نصب کنید تا بتوانید آن را در هر کجا استفاده کنید، باید دستور زیر را نیز اجرا کنید:

go install entgo.io/ent/cmd/ent

بعد از اتمام نصب، می‌توانید با اجرای ent -h بررسی کنید که آیا ابزار ent به درستی نصب شده است و دستورها و دستورالعمل‌های موجود را ببینید.

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 بسیار شفاف و آسان برای گسترش باشد و همچنین از نوعیت قوی زبان Go بهره ببرد.

3.1 اجرای تولید کد

اجرای ent generate برای تولید کد، یک مرحله حیاتی در چارچوب ent است. با این دستور، ent فایل‌های کد متناظری بر اساس طرح‌های تعریف شده، تولید خواهد کرد که کار توسعه‌ی بعدی را آسان می‌کند. دستور اجرای تولید کد به شکل ساده زیر است:

go generate ./ent

این دستور باید در ریشه پروژه اجرا شود. هنگامی که go generate فراخوانی می‌شود، به دنبال تمام فایل‌های Go می‌گردد که شامل شرح‌های خاصی هستند و دستورات مشخص شده در شرح‌ها را اجرا می‌کند. در مثال ما، این دستور مولد کد برای ent را مشخص می‌کند.

حتماً اطمینان حاصل کنید که مقدماتی‌سازی طرح و افزودن فیلدهای ضروری انجام شده باشد قبل از اجرا. فقط در این صورت، کد تولیدشده شامل تمام بخش‌های ضروری خواهد بود.

3.2 Understanding the Generated Code Assets

موارد کد تولیدی شامل چندین مولفه هستند، هر کدام با توابع مختلفی:

  • شی‌های Client و Tx: برای تعامل با گراف داده استفاده می‌شوند. Client امکان ارائه متدها برای ایجاد تراکنش‌ها (Tx) یا انجام مستقیم عملیات پایگاه داده را فراهم می‌کند.

  • سازندگان CRUD: برای هر نوع طرح، سازندگانی برای ایجاد، خواندن، به روزرسانی و حذف را ایجاد می‌کند که منطق عملیات مرتبط با موجودیت مورد نظر را ساده می‌کند.

  • شی موجودیت (Go struct): برای هر نوع در طرح، ساختارهای Go متناظری ایجاد می‌کند که این ساختارها را به جداول در پایگاه داده نگاشت می‌کند.

  • بسته‌ی ثابت‌ها و پردیکیت‌ها: شامل ثابت‌ها و پردیکیت‌ها برای تعامل با سازندگان است.

  • بسته‌ی مهاجرت: یک بسته برای مهاجرت پایگاه داده، مناسب برای لهجه‌های SQL.

  • بسته‌ی گیریز: امکان اضافه کردن میان‌افزار تغییر را فراهم می‌کند که منطق سفارشی را قبل یا بعد از ایجاد، به‌روزرسانی یا حذف موجودیت‌ها اجرا می‌کند.

با بررسی کد تولیدی، می‌توانید درک عمیق‌تری از نحوه‌ی خودکار کردن کد دسترسی به داده برای طرح‌های شمای 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("running ent codegen: %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("running ent codegen: %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("running ent codegen: %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 را بررسی می‌کند. اگر یک فیلد این تگ را نداشته باشد، مولفه‌دهی کد متوقف و یک خطای برگشتی می‌دهد. این یک راه کار موثر برای حفظ تمیزی و سازگاری کدها است.