개발하고 싶은 초심자

220314 D+58 ORM, Sequelize(shortly-mvc) 본문

기술개념정리(in Javascript)

220314 D+58 ORM, Sequelize(shortly-mvc)

정새얀 2022. 3. 13. 22:13

1. ORM(Object Relational Mapping /객체-관계 매핑)

출처: 코드스테이츠

데이터베이스 데이터 <—매핑—> Object 필드

: 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것.
→ 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용하여 객체 모델과 관계형 모델 간에 불일치가 존재하는데,
이를 ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결한다.

→ 객체를 통해 간접적으로 데이터베이스 데이터를 다룬다. persistent API라고도 할 수 있다.
ex) JPA, Hibernate 등

ORM을 이용하면 따로 SQL문을 짤 필요 없이 객체를 통해 간접적으로 데이터베이스를 다룰 수 있다.

 

객체 지향 프로그래밍에서 객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메소드를 하나의 논리적인 단위로 묶은 복합적인 자료구조

 attribute: 더이상 쪼갤 수 없는 정보의 단위

 

① ORM의 장점
객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있게 도와준다.
→ ORM을 이용하면 SQL Query가 아닌 직관적인 코드(메소드)로 데이터를 조작할 수 있어 

개발자가 객체 모델로 프로그래밍하는 데 집중할 수 있도록 도와준다.

 

‣ 쿼리문을 익힐 필요가 없음

→ 원래 사용하는 언어만 사용해도 됨


‣ 선언문, 할당, 종료 같은 부수적인 코드가 없거나 급격히 줄어든다.
‣ 각종 객체에 대한 코드를 별도로 작성하기 때문에 코드의 가독성을 올려준다.
‣ SQL의 절차적이고 순차적인 접근이 아닌 객체 지향적인 접근으로 인해 생산성이 증가한다.
재사용 및 유지보수의 편리성이 증가한다.
→ ORM은 독립적으로 작성되어있고, 해당 객체들을 재활용 할 수 있다.
때문에 모델에서 가공된 데이터를 컨트롤러에 의해 뷰와 합쳐지는 형태로 디자인 패턴을 견고하게 다지는데 유리하다.

 

‣ 매핑정보가 명확하여, ERD를 보는 것에 대한 의존도를 낮출 수 있다.
‣ DBMS(DataBase Management System, 데이터베이스 관리 시스템)에 대한 종속성이 줄어든다.
→ 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하기 때문에 

RDBMS(Relatationl(관계형) DataBase Management Sysytem)의

데이터 구조와 Java의 객체지향 모델 사이의 간격을 좁힐 수 있다.

 

→ 대부분 ORM 솔루션은 DB에 종속적이지 않다.
종속적이지 않다는것은 구현 방법뿐만 아니라 많은 솔루션에서 자료형 타입까지 유효하다.


⇒ 프로그래머는 Object에 집중하므로

극단적으로 DBMS를 교체하는 거대한 작업에도 비교적 적은 리스크와 시간이 소요된다.
또한 자바에서 가공할경우 equals, hashCode의 오버라이드 같은 자바의 기능을 이용할 수 있고,

간결하고 빠른 가공이 가능하다.

 

② ORM의 단점
‣ 완벽한 ORM으로만 서비스를 구현하기가 어렵다.
‣ 사용하기는 편하지만 설계는 매우 신중하게 해야 한다.
‣ 프로젝트의 복잡성이 커질 경우 난이도 또한 올라갈 수 있다.
‣ 잘못 구현된 경우에 속도 저하 및 심각할 경우 일관성이 무너지는 문제점이 생길 수 있다.
‣ 일부 자주 사용되는 대형 쿼리는 속도를 위해 SP를 쓰는 등 별도의 튜닝이 필요한 경우가 있다.
‣ DBMS의 고유 기능을 이용하기 어렵다. 

(하지만 이건 단점으로만 볼 수 없다 : 특정 DBMS의 고유기능을 이용하면 이식성이 저하된다.)
‣ 프로시저가 많은 시스템에선 ORM의 객체 지향적인 장점을 활용하기 어렵다.
이미 프로시저가 많은 시스템에선 다시 객체로 바꿔야 하며, 그 과정에서 생산성 저하나 리스크가 많이 발생할 수 있다.

 

✷ Sequelize 메소드
✷ 모델 쿼리 파인더(Model query finder) - 공식문서

// orm 예시 코드
const db = require('../db');

class Model {
  static tableName = 'items'

  static async findAll() {
    return new Promise((resolve, reject) => {
      const queryString = `SELECT * FROM ${Model.tableName}`

      db.query(queryString, (err, result) => {
        if(err) {
          console.log(err)
          reject(err)
          return;
        }
        resolve(result)
      })
    })
  }

  static async findOne({ where }) {
    return new Promise((resolve, reject) => {
      const queryString = `SELECT * FROM ${Model.tableName} WHERE id=${where.id}`

      db.query(queryString, (err, result) => {
        if(err) {
          console.log(err)
          reject(err)
          return;
        }

        if(result.length > 0) {
          const {name, price, image} = result[0]
          const instance = new Model(name, price, image)
          instance.id = where.id;
          resolve(instance)
        }
        else {
          reject({ error: 'id에 매칭되는 레코드가 없습니다' })
        }
      })
    })
  }

  async save() {
    return new Promise((resolve, reject) => {
      const queryString = `INSERT INTO ${Model.tableName} (name, price, image) VALUES ("${this.name}", ${this.price}, "${this.image}")`

      db.query(queryString, (err, result) => {
        if(err) {
          console.log(err)
          reject(err)
          return;
        }
        this.id = result.insertId
        resolve(result)
      })
    })
  }

  async destroy() {
    return new Promise((resolve, reject) => {
      const queryString = `DELETE FROM ${Model.tableName} WHERE id=${this.id}`

      db.query(queryString, (err, result) => {
        if(err) {
          console.log(err)
          reject(err)
          return;
        }
        resolve(result)
      })
    })
  }
}

module.exports = Model

2. Sequelize supports the standard Associations

const A = sequelize.define('A', /* ... */);
const B = sequelize.define('B', /* ... */);

A.hasOne(B); // A HasOne B
A.belongsTo(B); // A BelongsTo B
A.hasMany(B); // A HasMany B
A.belongsToMany(B, { through: 'C' }); // A BelongsToMany B through the junction table C

일대일 - HasOne, BelongsTo

일대다 - HasMany, BelongsToMany

다대다 - BelongsToMany

 

 

3. shortly-mvc sprint

‣ short.ly: bit.ly 와 같이 긴 URL을 짧게 단축시켜주는 앱

urls 테이블을 만들어 원본 URL과 단축 URL의 방문 횟수를 기록한다.

 

ORM을 이용해 아래와 같은 테이블을 만들어보기.

// 완성된 urls 테이블의 스키마

mysql> describe urls;
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int          | NO   | PRI | NULL    | auto_increment |
| url       | varchar(255) | YES  |     | NULL    |                |
| title     | varchar(255) | YES  |     | NULL    |                |
| visits    | int          | YES  |     | NULL    |                |
| createdAt | datetime     | NO   |     | NULL    |                |
| updatedAt | datetime     | NO   |     | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+

 

 스프린트 진행 방법

① sequelize 및 sequelize-cli 설치

// 시퀼라이즈 라이브러리 설치
// 스프린트 진행 시점으로 latest stable version인 v6로 진행했음
npm install --save sequelize

// 시퀼라이즈-cli 설치
// -dev 옵션을 붙이는 이유는 배포 단계에서 필요한 모듈이 아님. 초기 단계에서만 필요한 모듈이라서.
// devDependencies에 설치하는 의존성 모듈들은 개발시에만 필요한 모듈들임.
npm install --save-dev sequelize-cli

// 부트스트래핑: 프로젝트 초기 단계를 자동으로 설정할 수 있도록 도와주는 일
// 시퀼라이즈 부트스트래핑 설치(프로젝트 시작 전 시작해야 하는 명령어)
// 이 명령어를 사용하면 models, migrations, config, seeders파일이 생성된다
npx sequelize-cli init

 

‣ sequelize-cli 설치 후 생성되는 폴더 종류

config: 데이터베이스에 연결하는 방법을 CLI에 알려주는 구성 파일이 포함되어 있음.

models: 프로젝트의 모든 모델을 포함

migrations: 모든 마이그레이션 파일 포함

seeders: 모든 시드 파일을 포함

 

② mysql password 변경

// config > config.json
// 원본 코드
// "password" 부분을 mysql password로 변경(""로 감싸주기)
{
  "development": {
    "username": "root",
    "password": null, 
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

 

③ cli를 통한 모델 생성

(공식문서 참조)

model:generate 명령을 사용하는데, 이 명령에는 두 가지 옵션이 필요하다.

name: 모델명

attributes: 모델 속성 목록

ex)

// 예시 형식
npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string

이 명령어의 의미는

‣ 폴더에 모델 파일 user을 만듭니다. models

 폴더와 같은 이름 XXXXXXXXXXXXXX-create-user.js으로 마이그레이션 파일을 만듭니다. migrations

라는 것이다.

 

스프린트에서 진행해야 하는 것은

‣ 모델은 엔티티를 객체로 표현한 형태로,

데이터 구조를 기술하고, 데이터에 수행할 수 있는 명령의 모음을 의미합니다.

(레퍼런스)

 url, title, visits 필드를 생성해야 합니다.

 id, createdAt, updatedAt 필드는 자동으로 생성됩니다.

 모델을 잘못 만든 경우, 생성된 파일을 직접 수정하거나 삭제 후 명령을 다시 실행할 수 있습니다.

 

공식문서를 참고하여 다음과 같이 요구하는 필드를 갖기 위한 모델 생성의 명령어는

npx sequelize-cli model:generate --name url --attributes url:string,title:string,visits:integer

이 된다.

 

models폴더 안에 url.js파일, migrations파일에 오늘날짜-create-url.js라는

파일이 생성된 것을 확인할 수 있다.

 

✷ 왜 varchar가 아니라 string이고, number가 아니라 integer일까?

sequelize는 단순히 mysql에서만 사용할 수 있는 것은 아니다.

프로그래밍에서 범용적으로 공통되는 문법을 사용하기 위해서 그렇게 써준다.

 

④ migration / model 초기값 설정하기

migrations > 오늘날짜-create.url.js 수정하기

visits: {
  type: Sequelize.INTEGER,
  defaultValue: 0 // 초기값이 0이므로 추가해주기
},

models > url.js 수정하기

(공식문서 참조 - Default Values 참조)

sequelize.define('User', {
  name: {
    type: DataTypes.STRING,
    defaultValue: "John Doe"
  }
});

객체 형태로 바꿔주는 것을 알 수 있다.

이를 참조하여 초기값을 설정해주려면

  url.init({
    url: DataTypes.STRING,
    title: DataTypes.STRING,
    visits: {
      type: DataTypes.INTEGER,
      defaultValue: 0} // 초기값이 0이므로 defaultValue를 0으로 지정해줌
  }, {

로 바꿔줄 수 있다.

 

⑤ 마이그레이션

: 스키마 변경에 따른 데이터 이주.

→ 데이터를 선택, 준비, 추출 및 변환하여 한 컴퓨터 저장 시스템에서 다른 컴퓨터 저장 시스템으로 영구적으로 전송하는 프로세스.

 

스프린트에서 마이그레이션을 실행하려면

npx sequelize-cli db:migrate

위와 같은 명령어를 실행하면 된다.

마이그레이션은 스키마가 변경이 될 때마다 실행해주어야 한다.

 

// migration한 내역을 지움(migration 이전으로 되돌림)
// 이 명령어는 자동으로 최근에 migration한 내역만 지움
npx sequelize-cli db:migrate:undo

// migration한 내역을 '전부' 지움
npx sequelize-cli db:migrate:undo:all

⑥ Controller 만들기

controllers > links > index.js 생성하기

 

⑦ Controller get / post / redirection 요청 보내기

‣ POST

⇒ 새로운 레코드가 생겼을 때 Id가 축약된 url이 된다

 

‣ GET

 

✷ redirection(리디렉션)

: 방문자에게 초기에 요청한 URL이 아닌 다른 URL을 제공하는 행위

→ 사이트가 새 주소로 이동했거나 여러 페이지를 하나의 페이지로 통합했을 때

    바로 그 사이트로 이동하게끔 할 수 있는데, 그럴 때 리디렉션을 사용할 수 있다.

 

‣ REDIRECT

/links/:id --> 축약된 url

id값으로 해당하는 테이블을 조회 → 원본 url로 리디렉션 visits 필드의 값 1 증가(방문 횟수를 기록하는 기능)

 

🌟 shortly-mvc sprint에서 중요했던 점

‣ 분기점을 나누어 어떻게 연결되어 있는 것인지에 대한 큰 그림 그려보기

‣ sequelize를 이용한 마이그레이션에 대한 개념을 정확히 알아두기(공식문서 많이 읽어보자! 공식문서에 답이 있다!)

‣ 리디렉션에 대해서 흐름 읽기.

 

Comments