一架梯子,一头程序猿,仰望星空!
Go Ent ORM框架教程 > 内容正文

代码生成


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. 初始化Schema

2.1 使用ent init初始化模板

创建新的schema文件是开始使用ent进行代码生成的第一步。您可以通过执行以下命令来初始化schema模板:

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

此命令将会创建两个新的schema文件:user.gopet.go,并且将它们放置在ent/schema目录下。如果ent目录不存在,该命令也会自动创建。

在项目的根目录下运行ent init命令是一个良好的实践,这样有助于维持项目目录的结构和清晰度。

2.2 Schema文件结构

ent/schema目录下,每个schema对应一个Go语言的源文件。Schema文件是定义数据库模型的地方,你可以在这里定义模型的字段(field)和边(edge)。

例如,在user.go文件中,可能会定义一个用户模型,包括用户名、年龄等字段,并且定义了用户和宠物之间的关系。同理,在pet.go文件中定义宠物模型和相关的字段,比如宠物的名字、类型等,并且定义宠物与用户之间的关系。

这些文件最终将被ent工具用来生成对应的Go代码,包括用于数据库操作的客户端代码和CRUD(创建、读取、更新、删除)操作代码。

// ent/schema/user.go
package schema

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

// User定义了User实体的Schema。
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 {
    // Edges将在下一个章节更详细地说明。
}

ent的schema文件使用Go语言的类型和函数来声明数据库模型的结构,包括模型中的字段和模型之间的关系,并且使用ent框架提供的API来定义这些结构。这种方式使得ent非常直观且易于扩展,同时也利用了Go语言强类型的优点。

3. 代码生成的执行

3.1 执行代码生成

执行ent generate来生成代码是ent框架的核心环节。通过此命令,ent会根据定义的模式(schemas)生成相应的Go代码文件,以便于后续的开发工作。执行代码生成的命令非常直接:

go generate ./ent

以上命令需要在项目的根目录下运行。当go generate被调用时,它会查找所有包含特定注释的Go文件,并执行注解中指定的命令。在我们的例子中,这个命令指定了ent的代码生成器。

请确保在执行前已经完成了模式的初始化和必需字段的添加,这样生成的代码才会包含所有必要的部分。

3.2 认识生成的代码资产

生成的代码资产包含了多个组件,每个组件都有不同的功能:

  • ClientTx 对象:用于与数据图进行交互。Client 提供了方法来创建事务(Tx),或直接执行数据库操作。

  • CRUD builders:为每个模式类型生成了创建(Create)、读取(Read)、更新(Update)、删除(Delete)的构建器,简化了对应实体的操作逻辑。

  • Entity object(Go struct):为模式中的每种类型生成了相应的Go结构体,这些结构体映射了数据库中的表。

  • Constants and predicates package:包含了用于与构建器交互的常量和谓词。

  • Migrate package:用于数据库迁移的包,适用于SQL方言。

  • Hook package:提供了添加变更中间件的功能,使得在创建、更新或删除实体前、后可以执行自定义逻辑。

可以通过查看生成的代码,深入理解ent框架如何为你的模式自动化数据访问代码。

4. 代码生成选项

ent generate命令支持多种选项,以定制化代码生成过程。通过下面的命令可以查询所有支持的生成选项:

ent generate -h

以下是一些常用的命令行选项:

  • --feature strings:扩展codegen,添加额外的功能。
  • --header string:覆盖codegen头文件。
  • --storage string:指定要在codegen中支持的存储驱动,默认为"sql"。
  • --target string:指定codegen的目标目录。
  • --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作为包在项目中的用法

entcent框架中用于代码生成的核心包。除了命令行工具外,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)
    }
}

使用这种方法时,可以在main函数中修改gen.Config结构体,以应用不同的配置项。根据需要调用entc.Generate函数,可以灵活地对代码生成进行控制。

6.2 entc的详细配置

entc提供了丰富的配置选项,允许开发者定制化生成的代码。例如,可以通过配置自定义钩子(Hooks)以对生成的代码进行检查或修改,或者通过依赖注入(Dependency Injection)向生成的代码注入外部依赖。

以下示例演示了如何为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.Clientio.Writer作为依赖注入到生成的客户端对象中。

7. Schema的描述输出

ent框架中,可以使用ent describe命令来以图形化形式输出schema的描述信息。这可以帮助开发者快速理解现有的实体和关系。

执行下列命令即可获取schema的描述:

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. 代码生成钩子 (Hooks)

8.1 钩子的概念

钩子(Hooks)是在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("struct tag %q is missing for field %s.%s", name, node.Name, field.Name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

在这个例子中,在生成代码之前,EnsureStructTag函数会检查每个字段是否都有json标签,如果有字段缺失这个标签,代码生成将中止,并返回错误。这是一种保持代码整洁和一致性的有效方法。