1. 이주 메커니즘 개요

1.1 이주의 개념과 역할

데이터베이스 이주는 데이터 모델의 변경 사항을 데이터베이스 구조에 동기화하는 과정으로, 데이터 지속성에 있어 중요한 작업입니다. 응용 프로그램 버전이 업데이트됨에 따라 데이터 모델은 필드 추가, 삭제, 인덱스 수정 등의 변경을 겪게 됩니다. 이주를 통해 개발자는 데이터베이스 구조와 데이터 모델 간의 일관성을 유지하면서 변경 사항을 버전별로 체계적으로 관리할 수 있습니다.

현대 웹 개발에서 이주 메커니즘은 다음과 같은 이점을 제공합니다:

  1. 버전 관리: 이주 파일은 데이터베이스 구조의 변경 이력을 추적하여 롤백 및 각 버전의 변경 사항을 이해하는 데 편리합니다.
  2. 자동 배포: 이주 메커니즘을 통해 데이터베이스 배포와 업데이트를 자동화하여 수동 개입 가능성을 줄이고 오류 위험을 감소시킵니다.
  3. 팀 협업: 이주 파일을 통해 팀원들은 다른 개발 환경에서 동기화된 데이터베이스 구조를 사용하여 협업 개발을 촉진합니다.

1.2 ent 프레임워크의 이주 특징

ent 프레임워크와의 이주 메커니즘 통합은 다음과 같은 특징을 제공합니다:

  1. 선언적 프로그래밍: 개발자는 엔티티의 Go 표현에만 집중하면 되며, ent 프레임워크가 엔티티를 데이터베이스 테이블로의 전환을 처리합니다.
  2. 자동 이주: ent는 DDL 문을 수동으로 작성할 필요 없이 자동으로 데이터베이스 테이블 구조를 생성하고 업데이트할 수 있습니다.
  3. 유연한 제어: ent는 외부 키 제약 조건 유무, 전역 고유 ID 생성 등 다양한 구성 옵션을 제공하여 다양한 이주 요구를 지원합니다.

2. 자동 이주 소개

2.1 자동 이주의 기본 원리

ent 프레임워크의 자동 이주 기능은 스키마 정의 파일(일반적으로 ent/schema 디렉토리에서 찾을 수 있음)을 기반으로 데이터베이스 구조를 생성합니다. 개발자가 엔티티와 관계를 정의한 후, ent는 데이터베이스의 기존 구조를 검사하고 테이블 생성, 컬럼 추가 또는 수정, 인덱스 생성 등에 해당하는 작업을 생성합니다.

또한, ent의 자동 이주 원칙은 "추가 모드"로 동작합니다: 기본적으로 기존의 테이블이나 열을 삭제하지 않고 새로운 테이블, 새로운 인덱스 또는 테이블에 열을 추가하며, 데이터 손실을 방지하고 데이터베이스 구조를 전방향으로 확장하기 쉽게 설계되었습니다.

2.2 자동 이주 사용하기

ent 자동 이주를 사용하는 기본 단계는 다음과 같습니다:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("MySQL 연결 실패: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // 데이터베이스 스키마 생성 또는 업데이트를 위한 자동 이주 수행
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("데이터베이스 스키마 생성 실패: %v", err)
    }
}

위 코드에서 ent.Open는 데이터베이스와의 연결을 설정하고 클라이언트 인스턴스를 반환하며, client.Schema.Create는 실제 자동 이주 작업을 실행합니다.

3. 자동 이주의 고급 응용

3.1 열 및 인덱스 삭제

어떤 경우에는 더 이상 필요하지 않은 열이나 인덱스를 데이터베이스 스키마에서 제거해야 할 수 있습니다. 이때 WithDropColumnWithDropIndex 옵션을 사용할 수 있습니다. 예를 들어:

// 열 및 인덱스를 삭제하기 위한 옵션을 포함하여 이주 실행
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

이 코드 조각은 자동 이주 중 열과 인덱스를 삭제하는 구성을 활성화합니다. ent는 이주 작업 실행 시 스키마 정의에 존재하지 않는 열과 인덱스를 삭제합니다.

3.2 전역 고유 ID

기본적으로 SQL 데이터베이스의 기본 키는 각 테이블마다 1부터 시작하여 다른 엔티티 유형이 동일한 ID를 공유할 수 있습니다. 일부 애플리케이션 시나리오에서는 GraphQL을 사용할 때와 같이 다른 엔티티 유형의 객체에 대한 ID에 전역적인 고유성을 제공해야 할 수 있습니다. ent에서는 이를 WithGlobalUniqueID 옵션을 사용하여 구성할 수 있습니다:

// 각 엔티티에 대해 전역 고유 ID를 사용하여 이주 실행
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("데이터베이스 스키마 생성 실패: %v", err)
}

WithGlobalUniqueID 옵션을 활성화한 후, ent는 전역적인 고유성을 달성하기 위해 ent_types라는 테이블에 각 엔티티에 대해 2^32 범위의 ID를 할당합니다.

3.3 오프라인 모드

오프라인 모드는 데이터베이스에서 직접 실행하는 대신 io.Writer에 스키마 변경을 기록하는 기능을 제공합니다. 이 기능은 변경 사항이 적용되기 전에 SQL 명령을 확인하거나 수동으로 실행할 SQL 스크립트를 생성하는 데 유용합니다. 예를 들어:

// 마이그레이션 변경 사항을 파일에 덤프
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("마이그레이션 파일 생성에 실패했습니다: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("데이터베이스 스키마 변경 내용 출력에 실패했습니다: %v", err)
}

이 코드는 마이그레이션 변경 사항을 migrate.sql이라는 파일에 작성합니다. 실제로 개발자들은 직접 표준 출력에 출력하거나 리뷰 또는 기록을 위해 파일에 작성할 수 있습니다.

4. 외래 키 지원 및 사용자 정의 후크

4.1 외래 키 활성화 또는 비활성화

Ent에서 외래 키는 엔티티(기본 객체) 간의 관계(엣지)를 정의함으로써 구현되며, 이러한 외래 키 관계는 데이터 무결성과 일관성을 강제하는 데 사용됩니다. 그러나 성능 최적화를 위해 또는 데이터베이스가 외래 키를 지원하지 않는 경우와 같이 특정 상황에서 이를 비활성화하기를 선택할 수 있습니다.

마이그레이션에서 외래 키 제약 조건을 활성화하거나 비활성화하려면 WithForeignKeys 구성 옵션을 통해 제어할 수 있습니다:

// 외래 키 활성화
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true), 
)
if err != nil {
    log.Fatalf("외래 키로 스키마 리소스 생성에 실패했습니다: %v", err)
}

// 외래 키 비활성화
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false), 
)
if err != nil {
    log.Fatalf("외래 키 없이 스키마 리소스 생성에 실패했습니다: %v", err)
}

이 구성 옵션은 Schema.Create를 호출할 때 전달되어 지정된 값에 따라 생성된 DDL에 외래 키 제약 조건을 포함할지를 결정합니다.

4.2 마이그레이션 후크의 적용

마이그레이션 후크는 마이그레이션 실행의 다른 단계에서 삽입되고 실행되는 사용자 정의 로직입니다. 데이터베이스 마이그레이션 전/후에 특정 로직을 수행하는 데 매우 유용하며, 마이그레이션 결과를 유효성 검사하거나 데이터를 사전으로 채우는 등의 작업에 사용됩니다.

사용자 정의 마이그레이션 후크를 구현하는 예시입니다:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // 마이그레이션 전에 실행할 사용자 정의 코드
        // 예: 로깅, 특정 선행 조건 확인 등
        log.Println("마이그레이션 전 사용자 정의 로직")

        // 다음 후크 또는 기본 마이그레이션 로직 호출
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }
        
        // 마이그레이션 후에 실행할 사용자 정의 코드
        // 예: 클린업, 데이터 마이그레이션, 보안 검사 등
        log.Println("마이그레이션 후 사용자 정의 로직")
        return nil
    })
}

// 마이그레이션에서 사용자 정의 후크 사용
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("사용자 정의 마이그레이션 후크 적용 중 오류 발생: %v", err)
}

후크는 복잡한 마이그레이션에 필수적이고 강력한 도구로, 필요할 때 직접적으로 데이터베이스 마이그레이션 동작을 제어할 수 있습니다.

5. 버전별 마이그레이션

5.1 버전별 마이그레이션 소개

버전별 마이그레이션은 데이터베이스 마이그레이션을 관리하는 패턴으로, 개발자들이 데이터베이스 구조 변경을 여러 버전으로 분할하여 각 버전에 특정 데이터베이스 수정 명령 집합을 포함시킬 수 있도록 합니다. 자동 마이그레이션에 비해 버전별 마이그레이션은 더 세밀하게 제어하여 데이터베이스 구조 변경의 추적 가능성과 회전 가능성을 보장합니다.

버전별 마이그레이션의 주요 이점은 전방 및 후방 마이그레이션(즉, 업그레이드 또는 다운그레이드)을 지원하여 개발자들이 필요에 따라 특정 변경을 적용, 롤백 또는 건너뛸 수 있도록 하는 것입니다. 팀 내에서 협업할 때 버전별 마이그레이션은 각 구성원이 동일한 데이터베이스 구조를 작업하도록 보장하여 일관성으로 인한 문제를 줄입니다.

자동 마이그레이션은 주로 돌이킬 수 없으며, 최신 엔티티 모델 상태에 맞는 SQL 문을 생성하고 실행하여 개발 초기 단계나 소규모 프로젝트에서 사용됩니다.

5.2 버전별 마이그레이션 사용하기

1. Atlas 도구 설치하기

버전화된 마이그레이션을 사용하기 전에 시스템에 Atlas 도구를 설치해야 합니다. Atlas는 여러 데이터베이스 시스템을 지원하는 마이그레이션 도구로, 데이터베이스 스키마 변경을 강력하게 지원합니다.

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. 현재 엔터티 정의를 기반으로 마이그레이션 파일 생성하기

atlas migrate diff migration_name \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. 애플리케이션 마이그레이션 파일

마이그레이션 파일이 생성되면, 이를 개발, 테스트 또는 프로덕션 환경에 적용할 수 있습니다. 일반적으로, 이러한 마이그레이션 파일을 먼저 개발 또는 테스트 데이터베이스에 적용하여 예상대로 실행되는지 확인합니다. 그런 다음, 동일한 마이그레이션 단계가 프로덕션 환경에서 실행됩니다.

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

마이그레이션 파일을 적용하려면 atlas migrate apply 명령을 사용하고, 대상 데이터베이스의 디렉토리(--dir)와 URL(--url)을 지정합니다.