TypeORM의 built-in migration 이용하기

이우길·2022년 7월 6일
3

NestJs

목록 보기
16/20
post-thumbnail

TypeORM을 이용한 Migration

Goal

  • TypeORM의 Migration 기능을 사용해보자.

Intro

이전 Database Migration에 관하여...(with go)라는 주제로 마이그레이션에 대해 다룬적이 있다.

현재는 Go를 사용하지 않고 typescript기반의 Nest를 사용하고 있으며 ORM으로는 TypeORM을 사용하고 있다. 그렇기 때문에 TypeORM에서 제공하는 Migration기능을 사용해보고자 글을 정리한다.

TypeORM이 0.3 버전이 되면서 많은 것들이 변경되었으며 이번 글은 0.2.x버전을 기준으로 작성한다. (TODO: 0.3.x 버전 마이그레이션 내용 추가)


Migration이란?

마이그레이션이란 간단히 말해 데이터베이스의 이력을 관리이다. 소스코드를 형상관리(git)를 이용해 관리하는 것과 같이 데이테베이스 또한 Migration을 통하여 버전을 관리한다. 보통 변경할 사항에 대하여 변경되는 query와 해당 변경사항을 되돌릴 수 있는 query를 작성을 하게 된다.

TypeORMsynchronize라는 옵션이 존재하며 사용하면 데이터베이스와 엔티니의 sync를 맞출수는 있지만 변경사항 이전에 저장되어 있던 데이터에 문제가 생길 수 있기 때문에(sync를 맞추는 과정에서 기존 데이터가 전부 삭제되고 제로 베이스에서 시작하는 일이 발생함.) production 레벨의 애플리케이션에서는 해당 옵션을 false로 두고 Migration 전략을 가져가게 된다.


TypeORM에서 마이그레이션 시작하기

마이그레이션을 할 때 작업 순서는 아래와 같다.

  1. ormconfig.ts 파일 작성하기.

  2. package.json에서 script 작성하기.

  3. 마이그레이션 파일 생성 및 query 문 작성하기.

  4. 실행시키기.

ormconfig.ts 파일 작성하기.

TypeORM의 Connection 정보를 정의하는 ormconfig.ts를 작성한다.

export const typeOrmModuleOptions: TypeOrmModuleOptions = {
  type: "postgres",
  host: process.env.DB_HOST,
  port: process.env.DB_PORT5432,
  database: process.env.DB_DATABASE,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  synchronize: false,
  ...
};

export const OrmConfig = {
  ...typeOrmModuleOptions,
  migrationsTableName: "migrations",
  migrations: ["migrations/*.ts"],
  cli: {
    migrationsDir: "migrations",
  },
};
export default OrmConfig;

위에서 보면 typeOrmModuleOptionstypeOrmModuleOptions를 이용한 OrmConfig를 정의하였다. OrmConfigdefault export로 내보낸 이유는 이 후에도 설명을 하겠지만 Migration 옵션 중 -c(커넥션) 옵션을 부여하게 되는데 named export를 이용하여 내보내게 되면 TypeORM 라이브러리에서 어떠한 것을 사용해야 할 지 몰라 cli의 옵션이 적용되지 않는다.

그렇기 때문에 하나의 파일에서 한번만 사용할 수 있는 export default를 이용하여 OrmConfig를 내보내 준다.


package.json에서 script 작성하기.

yarn 명령와 같이 편하게 사용하기 위해 package.jsonscript에 아래와 같이 작성을 한다.

"scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    ...
    "typeorm": "node -r tsconfig-paths/register -r ts-node/register ./node_modules/typeorm/cli.js --config src/ormconfig.ts"
  },

script에서 tsconfig-paths/register를 추가한 이유는 tsconfig.json파일에서 정의한 타입 alias경로를 읽지 못하기 때문이다. 그렇기 때문에 tsconfig-paths/register를 이용하여 절대경로로 변경 후 사용하기 위함이다.

ts-node/registertypescript파일인 ormconfig.ts를 바로 읽지 못하고 트랜스파일 과정을 거쳐야 한다. 그렇기 때문에 ts-node를 이용하여 nodets파일을 읽을 수 있도록 하기 위함이다. (ts-node를 이용한다고 해서 트랜스파일 과정을 안거치는 것은 아니며 변환되는 js파일을 임시 폴더안에 두고 실행을 하는 것이다.)

--config src/ormconfig.ts 옵션은 커넥션 정보를 담고 있는 Object를 내보내는 ts파일의 위치를 정의하면 된다. 위에서 설명한 것과 같이 export default를 이용하여 내보낸 OrmConfig가 이 때 읽힌다.

위와 같은 설정을 하고 명령어 창에 yarn typeorm을 입력하면 도움말을 확인할 수 있으며 마이그레이션 관련해서 자주 사용하는 도움말들만 가져오면 아래와 같다.

  migration:create    Creates a new migration file.
  migration:generate  Generates a new migration file with sql needs to be executed to update schema.
  migration:run       Runs all pending migrations.
  migration:revert    Reverts last executed migration.

마이그레이션 파일 생성 및 query 문 작성하기.

TypeORM을 이용하여 마이그레이션을 진행할 때 migration:create 혹은 migration:generate를 이용할 수 있다.

migration:generate와 같은 경우에는 Migration generation drops and creates columns instead of altering resulting in data loss와 같은 이슈가 있다. 간단하게 정리하면 컬럼이 변경되면 해당 테이블의 컬럼을 DROP하고 재 생성하는 방식으로 이뤄진다. (migration:generate는 엔티티를 변경하고 실행하면 변경된 점에 대해 자동으로 마이그레이션 파일을 생성해준다.)

그렇기 때문에 현재 글은 migration:create를 기준으로 작성하겠다. migration:create와 같이 생성할 파일의 명을 입력하면 TypeORM Cli가 마이그레이션 파일을 생성해준다.

//명령어
yarn typeorm migration:create add_text_column

//생성된 파일의 code
export class addTextColumn1657093035722 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    //변경사항 query
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    //변경사항을 되돌릴 query
  }
}

마이그레이션 파일을 생성하는 명령어를 입력하면 이전 OrmConfig.ts에서 정의한 migrationsDir안으로 들어가게 된다. 생성된 파일의 메소드에서 up에는 변경사항 query를 작성하고 down에는 변경사항을 되돌릴 query를 작성한다.


실행시키기.

이제 마이그레이션을 실행시키는 일만 남았다. 마이그레이션 실행은 migration:runmigration:revert명령어를 통해 진행되며 직관적으로 runup을 실행 시키는 것이고 revertdown을 실행시키는 것이다.

migration:run or migration:revert를 실행시킬 때는 이전 package.json에서 정의한 --configOrmConfig.ts연결 정보를 가지고 진행을 하게 된다.

기본적으로 마이그레이션을 진행할 때 TRANSACTION안에서 진행되며 만약 진행을 하다 오류가 발생하면 ROLLBACK이 진행된다.

migrationsDir에 정의한 마이그레이션 파일이 여러개인 경우 migration:run을 하면 timestamp를 기준으로 제일 늦게 생성된 마이그레이션 파일까지 적용되게 된다. 하지만 migration:revert와 같은 경우 한 단계씩 내려간다.

처음 마이그레이션을 하는 경우 데이터베이스에 migrations라는 테이블이 생기며 이력들이 저장된다. (설정에서 migrationsTableName로 커스텀이 가능하다.)

  • 생성 된 테이블 구조

    idtimestampname
    11657093035722addTextColumn1657093035722

추가내용 : migrationsRun: true

배포를 할 때 마이그레이션을 진행하고 서버를 배포하거나 배포를 하고 마이그레이션을 진행하거나에 대한 고민을 해야한다. 그럴 때 사용할 수 있는 옵션이 migrationsRun: boolean이다.

마이그레이션 설정을 할 때 migrationsRun: boolean 옵션 true로 하게 되면 서버가 부트스트래핑 될 때 자동으로 마이그레이션을 진행하면서 서버가 구동된다. 만약 마이그레이션에 실패하면 서버는 부트스트래핑 되지 않으며 error를 뱉으며 계속해서 연결을 시도하다 종료된다.

해당 옵션을 사용할 때 마이그레이션 파일에 오류가 없는지 많은 검증 후 사용해야한다.


정리

TypeORM을 사용하고 있어서 built-in되어 있는 마이그레이션을 이용하였지만 이전에 사용해 봤던 golang-migrate더 편한 것 같기도...


REFERENCE

profile
leewoooo

0개의 댓글