Sequelize CLI & Migration 이해하기 - boostcamp-2020/IssueTracker-13 GitHub Wiki

Migration이란?

Database Migration(또는 Schema Migration)은 관계형 데이터베이스에서 Schema, 즉 테이블의 정의와 테이블 간 관계 구조를 Version Control을 통해서 점진적으로 관리하는 방법입니다. 간단하게 설명하자면, Database를 위한 Git 이라고 생각하실 수 있습니다. 우리가 Git과 GitHub를 통해서 코드의 변경이력을 추적하고, 때때로 에러 등의 이유로 다시 commit을 되돌리듯이 Database의 변화를 점진적으로 기록하고, 또 이 변화를 복원할 수 있도록 만들게 됩니다.

Agile 방법론에서는 빠르고 잦은 피드백을 통한 점진적인 소프트웨어의 개선을 추구합니다. 그러나, 데이터베이스는 그 특성 상 구조 변경에 수반되는 업무량과 리스크가 커서 이런 Agile 방법론에 맞추지 못하는 경우가 많습니다. Migration은 이런 단점을 개선하고, 관계형 데이터베이스의 관리를 조금 더 Agile하게 만들어준다고 볼 수 있습니다.

Migration의 장점?

  • DB 작업에 대한 실수를 간편하고 빠르게 복구할 수 있다.
  • 데이터 요구사항의 변화에 대응할 수 있다.
  • 다양한 개발환경에서 간편하게 DB를 세팅할 수 있다.

Sequelize-CLI는 무엇을 해주는가?

Sequelize-CLI는 위에서 소개한 Database Migration을 ORM 수준에서 지원해주는 툴입니다. DB 구조의 변경을 위해서 가능한 다른 방법은 model 파일들을 수정하고, 이를 현재 DB와 sync 시켜주는 방법이 있습니다. 하지만 model.sync({ force: true })를 할 경우, 변경점이 있는 model은 기존의 table을 drop 하고 아예 새로운 테이블을 추가하게 됩니다. 실제 production 환경(특히 무중단배포 환경)에서 이런 작업을 할 수 없기 때문에, Sequelize-CLI를 사용한 migration으로 DB 구조 변경을 하는 것이 더 안정적이라고 볼 수 있습니다.

Model 생성

아래와 같은 커맨드를 사용해서 테이블을 생성하는 migration 파일을 만들 뿐만 아니라, 해당 테이블에 대응되는 model 파일을 자동으로 생성해줍니다. migration 파일을 통해 DB에 테이블을 생성할 수 있습니다. model 파일은 추후 API 서버가 구동하면서 sequelize를 통해 DB 작업을 할 때 필요하게 됩니다.

Migration

migrations 폴더에 있는 개별 migration 파일들은 쉽게 DB의 변경점에 대한 commit이라고 생각하실 수 있습니다. Git에서 우리가 변경한 코드의 경우 자동적으로 이전 버전과 비교해서 변경점을 표시해주고, 코드 자체를 덮어씌워주게 됩니다. 그러나 DB의 경우 덮어쓰기가 불가능하기 때문에, 사용자가 스스로 이전 DB 구조에서 새로운 DB 구조로 변경해나가는 방법을 정의해주어야 합니다. Sequelize는 ORM으로서, 직접적인 mysql query 작업을 추상화했기 때문에 sql 문으로 테이블을 변경하는 것은 적합하지 않습니다. 따라서 Sequelize의 queryInterface를 통해 사용할 수 있는 ORM 수준으로 추상화된 함수들을 이용해서 변경사항을 정의하게 됩니다.

up, down을 정의하는 것은 사용자가 일일이 수동으로 함수를 이용해서 작업해주어야 합니다. 그러나 이 변경점들의 적용취소는 sequelize-CLI를 통해서 간편하게 할 수 있다는 것에 의의가 있습니다.

up

이전 DB 구조에서 다음 DB 구조로 변경하는 방법을 포함하는 함수입니다. DB 구조의 변경은 테이블의 생성, 테이블 열의 추가, 테이블 열 DataType의 변경 등이 있을 수 있습니다.

down

up이 수행한 작업을 취소시킬 수 있는 방법을 정의합니다. 테이블의 생성일 경우 반대로 테이블의 삭제, 열의 추가일 경우 열의 삭제, DataType의 변경일 경우 이전 DataType으로의 재변경을 정의하게 됩니다.

Seed

Migration과 비슷하지만, DB 구조의 변경 대신 테스트(또는 샘플) 데이터의 생성과 삭제를 담당합니다. Seed는 수행한 기록이 DB에 저장되지 않기 때문에, 수행할 때마다 동일한 작업을 수행하고, 동일한 샘플 데이터를 매번 DB에 삽입하게 됩니다.

Local에 Development DB 세팅하기

준비물

  • Local 환경의 MySQL Server
  • 위 서버의 접속을 위해 필요한 정보
    • username
    • password
    • database 이름
    • host 주소

세팅방법

현재 config/config.js 파일이 sequelize-cli의 DB 접속을 위해 필요한 환경설정을 담당하고 있습니다. Development 환경이라고 하더라도 GitHub와 같은 공개된 사이트에 접속정보를 업로드하는 것은 바람직하지 않습니다. 따라서 dotenv 라이브러리를 이용해서 사용자가 개별적으로 정의한 .env 파일을 참조하여 sequelize-cli의 접속 세팅을 해줍니다.

1. Database 생성

  • 사용하는 계정에 create database 권한이 있을 경우 npx sequelize-cli db:create DB명 을 통해서 새로운 database를 만들 수 있습니다.

  • 사용하는 계정에 create database 권한이 없을 경우 직접 mysql 서버에 root 권한으로 접속해서 create database DB명 커맨드를 사용해서 새로운 database를 만들어주어야 합니다.

2. DB 접속 환경설정

backend 폴더 안에 .env 파일을 생성하고, 아래와 같이 접속정보를 세팅해주세요. 호스트의 경우 localhost가 아닌 별도 주소를 사용하실 경우 해당 ip를 기입해주세요.

DEV_DB_USERNAME=myusername
DEV_DB_PASSWORD=mypassword
DEV_DB_NAME=mydbname
DEV_DB_HOST=127.0.0.1

3. 테이블 생성

npx sequelize-cli db:migrate 명령어를 사용해서 테이블들을 만들어주세요.

4. 샘플데이터 생성

npx sequelize-cli db:seed:all 명령어를 사용해서 모든 테이블의 샘플 데이터를 만들어주세요.

5. 결과 확인

mysql 서버에 workbench와 같은 DB Tool 또는 CLI로 접속해서 생성된 테이블과 샘플 데이터를 확인해주세요.

DB 작업과정

개별 Model 정의

1:1, 1:N, M:N 관계가 설정되지 않은 개별 Model 들을 정의합니다. Foreign Key 제약과 Column이 아직 세팅되어 있지 않습니다.

npx sequelize-cli model:generate --name User --attributes userName:string,password:string,profile:string,authType:string,isDeleted:boolean

npx sequelize-cli model:generate --name Issue --attributes title:string,isOpen:boolean,isDeleted:boolean,preview:string

npx sequelize-cli model:generate --name Comment --attributes description:string

npx sequelize-cli model:generate --name Label --attributes title:string,description:string,color:string,backgroundColor:date,isDeleted:boolean

npx sequelize-cli model:generate --name Milestone --attributes title:string,description:string,dueDate:date,isDeleted:boolean

models 폴더에 모델 파일들이 생성되며, 테이블을 실제로 DB에 만들어주기 위한 migration 파일들이 생성됩니다.

migration_models

Sequelize-CLI의 db:migrate 명령어를 사용하여, migration 파일들을 모두 development DB에 반영합니다. 현재 모든 migration 파일이 미반영 상태이기 때문에, 정의된 순서대로 모든 migration 파일들이 실행됩니다.

npx sequelize-cli db:migrate

local에 설정한 development DB에 실제로 테이블들이 생성된 것을 확인할 수 있습니다.

dev_db_tables

1:1 관계 정의

현재 ERD 상 1:1 association이 존재하지 않으므로 생략합니다. 1:N 관계와 설정방법이 동일하며, 양쪽 테이블이 동등하므로 한쪽을 선정해서 다른쪽 테이블의 id를 Foreign Key로 정의해주면 됩니다.

1:N 관계 정의

1:N 관계의 경우, N쪽의 table에서 1쪽 table의 key 값을 foreign key로 테이블에 추가하는 것으로 표현합니다. ERD에서 User와 Issue의 관계(author)를 예시로 사용하도록 하겠습니다.

  1. 양쪽 model 파일에서 association(hasMany, belongsTo)을 선언하기
  • models/user.js

user_hasmany

  • models/issue.js

issue_belongsto

  1. Migration 파일을 통해 1:N에서 N쪽의 model에 Foreign Key Column을 추가하기
  • up

oneToMany_up

  • down

oneToMany_down

M:N 관계 정의

M:N 관계의 경우, 양쪽 테이블의 key를 복합키로 가지는 새로운 테이블을 추가하는 것으로 표현합니다. ERD에서 User와 Issue의 관계(assignees)를 예시로 사용하도록 하겠습니다.

  1. 양쪽 model 파일에서 association(belongsToMany)을 선언하기
  • models/user.js

issue_belongstomany

  • models/issue.js

user_belongstomany

  1. Migration 파일을 통해 junction table(M:N을 표현하는 테이블)을 생성하기
  • up

manyToMany_up

  • down

manyToMany_down

Seed 생성

npx sequelize-cli seed:generate --name sample-user 명령어로 seeder 파일을 생성합니다.

seeder 파일 안에 샘플 데이터를 정의합니다.

'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.bulkInsert('Users', [{
      userName: 'junsushin-dev',
      password: 'junsushin-devpw',
      profile: 'https://avatars3.githubusercontent.com/u/32405358?s=400&u=cbda272c344b4c9e35cc1ee452f0bc4eae7e34c3&v=4',
      authType: 'local',
      isDeleted: false,
      createdAt: new Date(),
      updatedAt: new Date(),
    },
    {
      userName: 'parkdit94',
      password: 'parkdit94pw',
      profile: 'https://avatars1.githubusercontent.com/u/52442237?s=400&u=48c1ef65d9f5094f65f99e02dd1191656c57425f&v=4',
      authType: 'local',
      isDeleted: false,
      createdAt: new Date(),
      updatedAt: new Date(),
    },
    {
      userName: 'passwd10',
      password: 'pwsswd10pw',
      profile: 'https://avatars0.githubusercontent.com/u/68668924?s=400&v=4',
      authType: 'local',
      isDeleted: false,
      createdAt: new Date(),
      updatedAt: new Date(),
    }]);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('Users', null, {});
  },
};

npx sequelize-cli db:seed:all 명령어를 이용해서 seeder의 데이터를 DB로 삽입합니다.

참고자료