1. 모델 및 필드 기본 사항

1.1. 모델 정의 소개

ORM 프레임워크에서 모델은 애플리케이션의 엔터티 유형과 데이터베이스 테이블 간의 매핑 관계를 설명하는 데 사용됩니다. 모델은 엔터티의 속성과 관계뿐만 아니라 그와 관련된 데이터베이스 구성을 정의합니다. ent 프레임워크에서는 모델을 일반적으로 UserGroup과 같은 그래프에서 엔터티 유형을 설명하는 데 사용합니다.

모델 정의에는 일반으로 엔터티의 필드(또는 속성)와 엣지(또는 관계)에 대한 설명 및 일부 데이터베이스별 옵션이 포함됩니다. 이러한 설명은 엔터티의 구조, 속성 및 관계를 정의하는 데 도움이 되며, 모델에 따라 해당하는 데이터베이스 테이블 구조를 생성하는 데 사용될 수 있습니다.

1.2. 필드 개요

필드는 엔터티 속성을 나타내는 모델의 일부입니다. 엔터티의 속성인 이름, 나이, 날짜 등을 정의합니다. ent 프레임워크에서 필드 유형에는 정수, 문자열, 부울, 시간 등과 같은 다양한 기본 데이터 유형뿐만 아니라 UUID, []byte, JSON 등과 같은 몇 가지 SQL별 유형이 포함됩니다.

다음 표는 ent 프레임워크에서 지원하는 필드 유형을 보여줍니다:

유형 설명
int 정수형
uint8 부호 없는 8비트 정수형
float64 부동 소수점 형
bool 부울형
string 문자열
time.Time 시간
UUID UUID 형
[]byte 바이트 배열 형 (SQL 전용)
JSON JSON 형 (SQL 전용)
Enum Enum 형 (SQL 전용)
Other 기타 유형 (예: Postgres 범위)

2. 필드 속성 세부 정보

2.1. 데이터 유형

엔터티 모델의 속성 또는 필드의 데이터 유형은 저장될 수 있는 데이터 형식을 결정합니다. 이는 ent 프레임워크에서 모델 정의의 중요한 부분입니다. 아래는 ent 프레임워크에서 자주 사용되는 데이터 유형 중 일부입니다.

import (
    "time"

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

// 사용자 스키마.
type 사용자 struct {
    ent.Schema
}

// 사용자의 필드.
func (사용자) 필드() []ent.Field {
    return []ent.Field{
        field.Int("나이"),             // 정수형
        field.String("이름"),         // 문자열
        field.Bool("활성화"),         // 부울형
        field.Float("점수"),         // 부동 소수점 형
        field.Time("생성일자"),     // 타임스탬프 형
    }
}
  • int: 정수값을 나타냅니다. int8, int16, int32, int64 등이 될 수 있습니다.
  • string: 문자열 데이터를 나타냅니다.
  • bool: 부울 값으로, 일반적으로 플래그로 사용됩니다.
  • float64: 부동 소수점 숫자를 나타냅니다. float32를 사용할 수도 있습니다.
  • time.Time: 시간을 나타내며, 타임스탬프나 날짜 데이터에 사용됩니다.

이러한 필드 유형은 기본 데이터베이스에서 지원하는 해당 유형으로 매핑됩니다. 또한 entUUID, JSON, 열거형(Enum)과 같은 더 복잡한 유형을 지원하며 []byte (SQL 전용), Other (SQL 전용)와 같은 특수 데이터베이스 유형을 지원합니다.

2.2. 기본 값

필드에는 기본 값을 설정할 수 있습니다. 엔티티를 생성할 때 해당 값이 지정되지 않으면 기본 값이 사용됩니다. 기본 값은 고정된 값이나 함수로 동적으로 생성된 값일 수 있습니다. 정적 기본 값을 설정하려면 .Default 메서드를 사용하고, 동적으로 생성된 기본 값을 설정하려면 .DefaultFunc을 사용하세요.

// 사용자 스키마.
func (사용자) 필드() []ent.Field {
    return []ent.Field{
        field.Time("생성일자").
            Default(time.Now),  // time.Now의 고정된 기본 값
        field.String("역할").
            Default("사용자"),   // 고정된 문자열 값
        field.Float("점수").
            DefaultFunc(func() float64 {
                return 10.0  // 함수로 생성된 기본 값
            }),
    }
}

2.3. 필드 선택 사항 및 제로 값

기본적으로 필드는 필수입니다. 선택 사항 필드를 선언하려면 .Optional() 메서드를 사용하십시오. 선택 사항 필드는 데이터베이스에서 nullable 필드로 선언됩니다. Nillable 옵션을 사용하여 필드가 명시적으로 nil로 설정될 수 있게 하여 필드의 제로 값과 미설정 상태를 구분할 수 있습니다.

// 사용자 스키마
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("nickname").Optional(), // 선택 사항 필드는 필수가 아닙니다
        field.Int("age").Optional().Nillable(), // Nillable 필드는 nil로 설정될 수 있습니다
    }
}

위에서 정의된 모델을 사용할 때 age 필드는 nil 값 및 비-nil 제로 값 모두를 수용할 수 있습니다.

2.4. 필드 고유성

고유 필드는 데이터베이스 테이블에서 중복 값이 없도록 보장합니다. 고유 필드를 정의하려면 Unique() 메서드를 사용하십시오. 사용자 이메일 또는 사용자 이름과 같이 데이터 무결성을 중요시하는 경우 고유 필드를 사용해야 합니다.

// 사용자 스키마
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),  // 중복된 이메일 주소를 피하기 위한 고유 필드
    }
}

이는 중복 값을 삽입하는 것을 방지하기 위해 기본 데이터베이스에 고유한 제약 조건을 생성합니다.

2.5. 필드 색인화

필드 색인화는 특히 대규모 데이터베이스에서 데이터베이스 쿼리의 성능을 향상시키는 데 사용됩니다. ent 프레임워크에서는 .Indexes() 메서드를 사용하여 색인을 생성할 수 있습니다.

import "entgo.io/ent/schema/index"

// 사용자 스키마
func (User) Indexes() []ent.Index {
    return []ent.Index{
        index.Fields("email"),  // 'email' 필드에 색인 생성
        index.Fields("name", "age").Unique(), // 고유한 복합 색인 생성
    }
}

색인은 빈번히 쿼리되는 필드에 활용될 수 있지만, 너무 많은 색인은 쓰기 작업 성능이 감소할 수 있음을 주의해야 합니다. 따라서 색인을 생성할 결정은 실제 상황을 바탕으로 균형을 이루어야 합니다.

2.6. 사용자 지정 태그

ent 프레임워크에서 StructTag 메서드를 사용하여 생성된 엔터티 구조 필드에 사용자 지정 태그를 추가할 수 있습니다. 이러한 태그는 JSON 인코딩 및 XML 인코딩과 같은 작업을 구현하는 데 매우 유용합니다. 아래 예제에서는 name 필드에 대한 사용자 지정 JSON 및 XML 태그를 추가합니다.

// 사용자의 필드
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").
            // StructTag 메서드를 사용하여 사용자 지정 태그 추가
            // 여기서 name 필드의 JSON 태그를 'username'으로 설정하고 필드가 비어있을 때는 무시합니다 (omitempty)
            // 또한 인코딩을 위한 XML 태그를 'name'으로 설정합니다
            StructTag(`json:"username,omitempty" xml:"name"`),
    }
}

JSON 또는 XML로 인코딩할 때 omitempty 옵션은 name 필드가 비어 있으면 해당 필드를 인코딩 결과에서 제외함을 나타냅니다. 이것은 API를 작성할 때 응답 본문의 크기를 줄이는 데 매우 유용합니다.

또한 동일한 필드에 대해 여러 태그를 동시에 설정하는 방법을 보여주고 있습니다. JSON 태그는 json 키를 사용하고, XML 태그는 xml 키를 사용하며, 공백으로 구분합니다. 이러한 태그는 encoding/jsonencoding/xml과 같은 라이브러리 함수가 구조체를 파싱하여 인코딩 또는 디코딩할 때 사용됩니다.

3. 필드 유효성 검사 및 제약 조건

필드 유효성 검사는 엔터티 모델의 데이터 일관성과 유효성을 보장하기 위한 중요한 측면입니다. 이 섹션에서는 내장 유효성 검사자, 사용자 정의 유효성 검사자 및 다양한 제약 조건을 사용하여 데이터의 무결성과 품질을 향상하는 방법에 대해 알아보겠습니다.

3.1. 내장 유효성 검사기

프레임워크는 다양한 유형의 필드에 대한 일반적인 데이터 유효성 검사를 수행하기 위한 일련의 내장 유효성 검사기를 제공합니다. 이러한 내장 유효성 검사기를 사용하면 개발 프로세스를 간소화하고 필드의 유효한 데이터 범위나 형식을 빠르게 정의할 수 있습니다.

다음은 일부 내장 필드 유효성 검사기의 예시입니다:

  • 숫자 유형에 대한 유효성 검사기:
    • Positive(): 필드 값이 양수인지 유효성을 검사합니다.
    • Negative(): 필드 값이 음수인지 유효성을 검사합니다.
    • NonNegative(): 필드 값이 음이 아닌지 유효성을 검사합니다.
    • Min(i): 필드 값이 주어진 최솟값 i보다 큰지 유효성을 검사합니다.
    • Max(i): 필드 값이 주어진 최댓값 i보다 작은지 유효성을 검사합니다.
  • string 유형에 대한 유효성 검사기:
    • MinLen(i): 문자열의 최소 길이를 유효성을 검사합니다.
    • MaxLen(i): 문자열의 최대 길이를 유효성을 검사합니다.
    • Match(regexp.Regexp): 문자열이 주어진 정규 표현식과 일치하는지 유효성을 검사합니다.
    • NotEmpty: 문자열이 비어있지 않은지 유효성을 검사합니다.

실용적인 코드 예시를 살펴보겠습니다. 이 예시에서는 User 모델이 생성되며, 이 모델은 음이 아닌 정수형 age 필드와 지정된 형식의 email 필드를 포함합니다:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Int("age").
            Positive(),
        field.String("email").
            Match(regexp.MustCompile(`^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$`)),
    }
}

3.2. 사용자 정의 유효성 검사기

내장 유효성 검사기는 많은 일반적인 유효성 검사 요구를 처리할 수 있지만 때로는 더 복잡한 유효성 검사 로직이 필요할 수 있습니다. 이러한 경우에는 특정 비즈니스 규칙을 충족시키기 위해 사용자 정의 유효성 검사기를 작성할 수 있습니다.

사용자 정의 유효성 검사기는 필드 값 받아들이고 error를 반환하는 함수입니다. 반환된 error가 비어있지 않으면 유효성 실패를 나타냅니다. 사용자 정의 유효성 검사기의 일반적인 형식은 다음과 같습니다:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone").
            Validate(func(s string) error {
                // 전화 번호가 예상 형식을 충족하는지 확인
                matched, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
                if !matched {
                    return errors.New("잘못된 전화번호 형식")
                }
                return nil
            }),
    }
}

위 예시에서는 전화번호 형식을 검증하기 위한 사용자 정의 유효성 검사기를 만들었습니다.

3.3. 제약 조건

제약 조건은 데이터베이스 객체에 특정 규칙을 강제하는 규칙입니다. 이들은 잘못된 데이터 입력을 방지하거나 데이터의 관계를 정의하는 등 데이터의 정확성과 일관성을 보장하는 데 사용할 수 있습니다.

일반적인 데이터베이스 제약 조건에는 다음이 있습니다:

  • 기본 키 제약: 테이블의 각 레코드가 고유함을 보장합니다.
  • 고유 제약: 테이블에서 열 값 또는 열 조합의 고유성을 보장합니다.
  • 외래 키 제약: 테이블 간의 관계를 정의하며 참조 무결성을 보장합니다.
  • 확인 제약: 필드 값이 특정 조건을 충족하도록 보장합니다.

엔티티 모델에서 데이터 무결성을 유지하기 위해 제약 조건을 다음과 같이 정의할 수 있습니다:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("username").
            Unique(), // 테이블에서 username이 고유한지를 보장하는 고유 제약.
        field.String("email").
            Unique(), // 테이블에서 email이 고유한지를 보장하는 고유 제약.
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("friends", User.Type).
            Unique(), // 다른 사용자와의 고유한 엣지 관계를 생성하는 외래 키 제약.
    }
}

요약하면, 필드 유효성 검사와 제약 조건은 좋은 데이터 품질을 보장하고 예기치 않은 데이터 오류를 피하기 위해 중요합니다. ent 프레임워크에서 제공하는 도구를 활용하면 이 프로세스를 간단하고 신뢰할 수 있게 만들 수 있습니다.