Basically, I have been using GORM for Go ORM until a friend recommended entgo some time ago. Until some time ago, a friend recommended entgo, and after trying it, I found that entgo is a better choice. entgo is an open source go generate based ORM from Facebook, but it is not too complicated. The advantage over GORM is that in GORM, there are a lot of interface{} passes, so it is hard to check for errors that should not be there by the compiler, while entgo generates typed code by means of go generate. I personally think this is the biggest advantage of entgo.

Simple use

First you need to initialize a project, like go mod init github.com/jiajunhuang/test, and then install entgo.

1
2
$ go get entgo.io/ent/cmd/ent
$

Next you can start defining the schema, first generating an entity.

1
2
$ go run entgo.io/ent/cmd/ent init User
$

This is where entgo differs from GORM in that GORM puts the type in the code and uses it directly afterwards, whereas entgo defines the schema as more of a meta-information since it needs to generate some data operations.

After generating, edit the schema.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package schema

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

// User 定义User类型
type User struct {
    ent.Schema
}

// Fields 方法写明有什么字段
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Int("age").Positive(),
        field.String("name").Default("unknown"),
    }
}

// Edges 写明与其它schema的关系,相当于ER图中的关系
func (User) Edges() []ent.Edge {
    return nil
}

// Indexes 写明索引
func (User) Indexes() []ent.Index {
    return []ent.Index{
        // 非唯一的联合索引
        index.Fields("age", "name"),
        // 非唯一的普通索引
        index.Fields("age"),
        // 唯一索引
        index.Fields("name").Unique(),
    }
}

Then execute the command to generate the code.

1
2
$ go generate ./ent
$

This is the time to start importing packages and using them. For convenience, see the official website example directly at https://entgo.io/docs/crud

Note that in the where example, the lowercase user is to be imported from the package.

1
import "github.com/jiajunhuang/test/env/user"

Another thing is that you can also set Mutation in Fields() if you don’t want the data to be modified.

1
2
3
4
5
func (Pair) Fields() []ent.Field {
    return []ent.Field{
        field.String("pair_symbol").MaxLen(64).NotEmpty().Immutable(),
    }
}

Reuse methods

Some definitions can be reused via Mixin.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package schema

import (
    "time"

    "entgo.io/ent"
    "entgo.io/ent/schema/field"
    "entgo.io/ent/schema/mixin"
)

// -------------------------------------------------
// Mixin definition

// TimeMixin implements the ent.Mixin for sharing
// time fields with package schemas.
type TimeMixin struct {
    // We embed the `mixin.Schema` to avoid
    // implementing the rest of the methods.
    mixin.Schema
}

func (TimeMixin) Fields() []ent.Field {
    return []ent.Field{
        field.Time("created_at").
            Immutable().
            Default(time.Now),
        field.Time("updated_at").
            Default(time.Now).
            UpdateDefault(time.Now),
        field.Bool("deleted").Default(false),
    }
}
1
2
3
4
5
6
// 要记得在User里增加这个方法
func (User) Mixin() []ent.Mixin {
    return []ent.Mixin{
        TimeMixin{},
    }
}

Migration

Database Migration is a very important thing. entgo comes with it, directly in the code.

1
2
3
if err := client.Schema.Create(ctx); err != nil {
    log.Fatalf("failed creating schema resources: %v", err)
}

Auto-migration, by default, only adds but does not subtract. If you want to be able to delete indexes and columns that do not exist, you can add the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
    "context"
    "log"
    
    "<project>/ent"
    "<project>/ent/migrate"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("failed connecting to mysql: %v", err)
    }
    defer client.Close()
    ctx := context.Background()
    // Run migration.
    err = client.Schema.Create(
        ctx, 
        migrate.WithDropIndex(true),
        migrate.WithDropColumn(true), 
    )
    if err != nil {
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

Of course, it is possible to use libraries such as go-migrate without it.

Summary

This is the basic usage of entgo. There are many advanced uses of entgo that are not described here, so check the official documentation if you are interested.