entgo
는 Facebook Open Source에서 관리하는 go언어 ORM라이브러리 입니다.
entgo를 만나기 전에는 xorm
을 사용했는데, xorm
보다 깔끔하고, 사용하기 편하다는 인상을 남겨주었습니다.
entgo 설치
todo
프로젝트 폴더를 만듭니다.
mkdir todo
cd todo
go mod init todo
entgo
라이브러리를 설치합니다.
go get entgo.io/ent/cmd/ent
entgo
구성을 위해 다음 명령어를 입력합니다.
go run entgo.io/ent/cmd/ent init User
다음처럼 ent
폴더가 생성됩니다.
./
├── ent
│ ├── generate.go
│ └── schema
│ └── user.go
├── go.mod
└── go.sum
todo/ent/schema/user.go
package schema
import "entgo.io/ent"
// Todo holds the schema definition for the Todo entity.
type User struct {
ent.Schema
}
// Fields of the Todo.
func (User) Fields() []ent.Field {
return nil
}
// Edges of the Todo.
func (User) Edges() []ent.Edge {
return nil
}
Fields()
함수로 테이블의 칼럼(필드) 를 정의합니다.Edges()
함수로 테이블끼리 의 관계를 정의합니다.
필드 정의하기
entgo에서 지원하는 자료형 입니다.
go |
---|
숫자 자료형 ex) int , uint8 , float64 ,... |
bool |
string |
time.Time |
[]byte |
JSON |
Enum |
UUID |
Other |
테이블을 정의하기 위해 Fields()
함수를 다음처럼 작성합니다.
func (User) Fields() []ent.Field {
return []ent.Field {
field.String("name"),
field.Int("age"),
field.String("phone"),
}
}
다음 테이블이 정의됩니다.
todo | ||||
---|---|---|---|---|
colomn | id | name | age | phone |
go type | - | string |
int |
string |
sql type | - | text |
int |
text |
id는 entgo
에서 자동으로 생성되고, 테이블에 값을 추가할때마다 auto increase 됩니다. field이름은 기본적으로 snake_case
를 따라야 합니다.
필드 기본 값 - Default()
Default()
함수로 필드의 기본 값을 설정 합니다. age
의 기본 값을 0
으로 설정 했습니다.
func (User) Fields() []ent.Field {
return []ent.Field {
field.String("name"),
field.Int("age").
Default(0),
field.String("phone"),
}
}
검증자 - Validate()
값 입력 전 정규식으로 값이 올바른지 확인할 수 있습니다. Match()
함수로 정규식을 사용하거나, Validate()
함수로 검증자 함수를 넣어줄 수 있습니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Match(regexp.MustCompile("[a-zA-Z_]+$")).
Validate(func(s string) error {
if strings.ToLower(s) == s {
return errors.New("group name must begin with uppercase")
}
return nil
}),
field.Int("age").
Default(0),
field.String("phone"),
}
}
빌트인 검증자
정수형 | |
---|---|
Positive() | 양수만 허용 |
Negative() | 음수만 허용 |
NonNegative() | 음수 비허용 |
Min(i) | i 초과 값만 |
Max(i) | 미만 값만 |
Range(i, j) | i ~ j 값만 |
string | |
---|---|
MinLen(i) | 최소 길이 |
MaxLen(i) | 최대 길이 |
Match(regexp.Regexp) | 정규식 검사 |
NotEmpty | "" 비허용 |
필드 Null 허용 - Optional()
기본적으로 entgo
가 생성한 필드는 null
을 불허 합니다. nullable필드를 만들기위해 Optional()
함수를 사용합니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Optional(),
field.Int("age").
Default(0),
field.String("phone"),
}
}
Go Type Null 허용 - Nillable()
Optional()
과 같이 사용합니다. Optional()
처럼 Nullable한 필드를 생성하는 것은 같으나, 코드 제네레이션후 구조체에 nil
값을 저장할 수 있는 형태로 만들어집니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Optional(),
field.String("sub").
Optional().Nillable(),
}
}
코드 제네레이션 후 다음 구조체가 생성 됩니다.
type User struct {
// Optional()
Name string `json:"name,omitempty"`
// Optional(), Nillable()
Sub *string `json:"sub,omitempty"`
}
불변 - Immutable()
입력한 값이 변경되지 않길 원하면, Immutable()
를 사용하면 됩니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
field.Time("created_at").
Default(time.Now).
Immutable(),
}
}
유니크 - Unique()
입력한 값이 칼럼 내에서 중복되지 않는 고유한 값이길 원하면, Unique()
를 사용하면 됩니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
field.String("nickname").
Unique(),
}
}
스토리지 키 - StorageKey()
커스텀 스토리지키를 지정합니다. Gremlin
에서 커스텀 칼럼 이름을 매핑하는등에 사용합니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
StorageKey("old_name"),
}
}
Struct Tags
코드 제네레이션후 생성될 구조체에 태그를 지정할 때 사용합니다. 기본적으로 Json 태그는 entgo가 생성하니 따로 지정할 필요 없습니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
StructTag(`hello:"how are you"`),
}
}
민감한 필드 - Sensitive()
Sensitive()
를 설정하면, 코드 제네레이션 후 구조체에 태그가 붙지 않습니다. 그리고 해당 필드는 출력되거나 직렬화되지 않습니다.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("password").
Sensitive(),
}
}
코드 제네레이션
정의한 필드를 go
에서 사용하기위한 코드를 생성 합니다.
필드를 정의합니다.
func (User) Fields() []ent.Field {
return []ent.Field {
field.String("name").
Unique(),
field.String("sub_name").
Optional(),
field.Int("age").
Positive(),
field.String("password").
Sensitive(),
}
}
콘솔로 다음 명령어를 실행합니다.
go generate ./ent
go mod tidy
./ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── enttest
│ └── enttest.go
├── generate.go
├── hook
│ └── hook.go
├── migrate
│ ├── migrate.go
│ └── schema.go
├── mutation.go
├── predicate
│ └── predicate.go
├── runtime
│ └── runtime.go
├── runtime.go
├── schema
│ └── user.go
├── tx.go
├── user
│ ├── user.go
│ └── where.go
├── user_create.go
├── user_delete.go
├── user.go
├── user_query.go
└── user_update.go
위 처럼 ./ent
폴더내 파일이 생성 됩니다.
테스트
DB에 접속해서 사용해 보겠습니다.
다음 명령어로 테스트 파일을 생성 합니다.
touch main.go
touch todo.db
main.go
를 다음처럼 작성 합니다.
package main
import (
"context"
"log"
"todo/ent"
// ent가 정의한 user 구조체
"todo/ent/user"
// sqlite3 드라이버
_ "github.com/mattn/go-sqlite3"
)
func main() {
// sqlite3로 오픈
client, err := ent.Open("sqlite3", "./todo.db?_fk=1")
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// 스키마 생성
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
// user 'tom' 생성
_, err = client.User.
Create().
SetName("tom").
SetAge(50).
SetPassword("password1").
Save(ctx)
if err != nil {
log.Fatal(err)
}
// 유저 'bob' 생성
_, err = client.User.
Create().
SetName("bob").
SetAge(85).
SetPassword("password2").
Save(ctx)
if err != nil {
log.Fatal(err)
}
// 유저 'blue' 생성
_, err = client.User.
Create().
SetName("blue").
SetSubName("sky").
SetAge(99).
SetPassword("password").
Save(ctx)
if err != nil {
log.Fatal(err)
}
// 나이가 80보다 큰 유저만 쿼리
users, err := client.User.Query().Where(user.AgeGT(80)).All(ctx)
if err != nil {
log.Fatal(err)
}
for _, n := range(users) {
log.Printf("%#v\n", n)
}
}
터미널 출력
$ go run main.go
... , ID:2, Name:"bob", SubName:"", Age:85, Password:"password2"}
... , ID:3, Name:"blue", SubName:"sky", Age:99, Password:"password"}
Sqlite3
sqlite> select * from users;
1|tom||50|password1
2|bob||85|password2
3|blue|sky|99|password