1. Installing the ent
tool
To install the ent
code generation tool, you need to follow these steps:
First, make sure that your system has the Go language environment installed. Then, run the following command to obtain the ent
tool:
go get -d entgo.io/ent/cmd/ent
This command will download the code for the ent
tool, but it will not compile and install it immediately. If you want to install ent
to the $GOPATH/bin
directory so that you can use it anywhere, you also need to execute the following command:
go install entgo.io/ent/cmd/ent
Once the installation is complete, you can check if the ent
tool is properly installed and view the available commands and instructions by running ent -h
.
2. Initializing Schema
2.1 Initializing the Template Using ent init
Creating a new schema file is the first step to start using ent
for code generation. You can initialize the schema template by executing the following command:
go run -mod=mod entgo.io/ent/cmd/ent new User Pet
This command will create two new schema files: user.go
and pet.go
, and place them in the ent/schema
directory. If the ent
directory does not exist, this command will also create it automatically.
Running the ent init
command in the project's root directory is a good practice, as it helps maintain the project directory's structure and clarity.
2.2 Schema File Structure
In the ent/schema
directory, each schema corresponds to a Go language source file. Schema files are where you define the database model, including the fields and edges (relationships).
For example, in the user.go
file, you might define a user model, including fields such as username and age, and define the relationship between users and pets. Similarly, in the pet.go
file, you would define the pet model and its related fields, such as the pet's name, type, and the relationship between pets and users.
These files will ultimately be used by the ent
tool to generate corresponding Go code, including client code for database operations and CRUD (Create, Read, Update, Delete) operations.
// ent/schema/user.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// User defines the schema for the User entity.
type User struct {
ent.Schema
}
// The Fields method is used to define the entity's fields.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").Unique(),
field.Int("age").Positive(),
}
}
// The Edges method is used to define the entity's associations.
func (User) Edges() []ent.Edge {
// Associations will be explained in more detail in the next section.
}
ent
's schema files use Go language types and functions to declare the structure of the database model, including the fields and relationships between models, and use the API provided by the ent
framework to define these structures. This approach makes ent
very intuitive and easy to extend, while also taking advantage of the strong typing of Go language.
3.1 Running Code Generation
Running ent generate
to generate code is a crucial step in the ent
framework. With this command, ent
will generate corresponding Go code files based on the defined schemas, facilitating subsequent development work. The command for executing code generation is straightforward:
go generate ./ent
The above command needs to be run in the project's root directory. When go generate
is called, it will search for all Go files containing specific annotations and execute the commands specified in the annotations. In our example, this command specifies the code generator for ent
.
Ensure that schema initialization and necessary field additions have been completed before execution. Only then will the generated code include all the necessary parts.
3.2 Understanding the Generated Code Assets
The generated code assets contain multiple components, each with different functions:
-
Client and Tx objects: Used to interact with the data graph. The Client provides methods to create transactions (Tx) or directly execute database operations.
-
CRUD builders: For each schema type, it generates builders for create, read, update, and delete, simplifying the operation logic of the corresponding entity.
-
Entity object (Go struct): It generates the corresponding Go structs for each type in the schema, mapping these structs to the tables in the database.
-
Constants and predicates package: Contains constants and predicates for interacting with the builders.
-
Migrate package: A package for database migration, suitable for SQL dialects.
-
Hook package: Provides the functionality to add change middleware, allowing custom logic to be executed before or after creating, updating, or deleting entities.
By examining the generated code, you can gain a deeper understanding of how the ent
framework automates data access code for your schemas.
4. Code Generation Options
The ent generate
command supports various options to customize the code generation process. You can query all supported generation options through the following command:
ent generate -h
Here are some commonly used command-line options:
-
--feature strings
: Extends code generation, adding extra functionality. -
--header string
: Overrides the code generation header file. -
--storage string
: Specifies the storage driver supported in code generation, defaulting to "sql". -
--target string
: Specifies the target directory for code generation. -
--template strings
: Executes additional Go templates. Supports file, directory, and wildcard path, for example:--template file="path/to/file.tmpl"
.
These options allow developers to customize their code generation process according to different needs and preferences.
5. Storage Option Configuration
ent
supports generating code assets for both SQL and Gremlin dialects, with the default being SQL dialect. If the project needs to connect to a Gremlin database, the corresponding database dialect needs to be configured. The following demonstrates how to specify storage options:
ent generate --storage gremlin ./ent/schema
The above command instructs ent
to use the Gremlin dialect when generating code. This ensures that the generated assets are tailored to the requirements of the Gremlin database, ensuring compatibility with a specific graph database.
6. Advanced Usage: entc
Package
6.1 Usage of entc
as a Package in the Project
entc
is the core package used for code generation in the ent
framework. In addition to the command-line tool, entc
can also be integrated into the project as a package, allowing developers to control and customize the code generation process within the code itself.
To use entc
as a package in the project, you need to create a file named entc.go
and add the following content to the file:
// +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)
}
}
When using this approach, you can modify the gen.Config
struct within the main
function to apply different configuration options. By calling the entc.Generate
function as needed, you can flexibly control the code generation process.
6.2 Detailed Configuration of entc
entc
provides extensive configuration options, allowing developers to customize the generated code. For example, custom hooks can be configured to inspect or modify the generated code, and external dependencies can be injected using dependency injection.
The following example demonstrates how to provide custom hooks for the entc.Generate
function:
func main() {
err := entc.Generate("./schema", &gen.Config{
Hooks: []gen.Hook{
HookFunction,
},
})
if err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}
// HookFunction is a custom hook function
func HookFunction(next gen.Generator) gen.Generator {
return gen.GenerateFunc(func(g *gen.Graph) error {
// Can handle the graph mode represented by g here
// For example, validate the existence of fields or modify the structure
return next.Generate(g)
})
}
Additionally, external dependencies can be added using 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)
}
}
In this example, we inject http.Client
and io.Writer
as dependencies into the generated client objects.
7. Output of Schema Description
In the ent
framework, the ent describe
command can be used to output the description of the schema in a graphical format. This can help developers quickly understand the existing entities and relationships.
Execute the following command to obtain the description of the schema:
go run -mod=mod entgo.io/ent/cmd/ent describe ./ent/schema
The above command will output a table similar to the following, displaying information such as fields, types, relationships, etc. for each entity:
User:
+-------+---------+--------+-----------+ ...
| Field | Type | Unique | Optional | ...
+-------+---------+--------+-----------+ ...
| id | int | false | false | ...
| name | string | true | false | ...
+-------+---------+--------+-----------+ ...
+-------+--------+---------+-----------+ ...
| Edge | Type | Inverse | Relation | ...
+-------+--------+---------+-----------+ ...
| pets | Pet | false | O2M | ...
+-------+--------+---------+-----------+ ...
8. Code Generation Hooks
8.1 Concept of Hooks
Hooks are middleware functions that can be inserted into the ent
code generation process, allowing custom logic to be inserted before and after generating code. Hooks can be used to manipulate the abstract syntax tree (AST) of the generated code, perform validation, or add additional code snippets.
8.2 Example of Using Hooks
Here's an example of using a hook to ensure that all fields contain a certain struct tag (e.g., 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 ensures that all fields in the graph contain a specific struct tag
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)
})
}
}
In this example, before generating the code, the EnsureStructTag
function checks each field for the json
tag. If a field is missing this tag, the code generation will terminate and return an error. This is an effective way to maintain code cleanliness and consistency.