TypeORM 리뷰

영태·2022년 8월 9일
0

[REVIEW]

목록 보기
6/7
post-thumbnail

ORM이란?

  • Object-Relational Mapping의 약자, 객체-관계 맵핑
  • 객체지향 프로그램과 관계형 DB의 데이터를 자동으로 맵핑해주는 것을 말합니다
    • 객체 지향 프로그래밍은 class를 사용합니다
    • 한편 RDB는 table을 사용합니다
    • 객체 모델과 RDB 모델 간의 불일치가 존재합니다
    • ORM은 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결해주며, 객체를 통해 간접적으로 데이터베이스를 조작하는 맵핑을 의미합니다

다음과 같이, 퓨어한 코드에서는 JS와 SQL문이 혼용된 복잡한 코드를 작성해야하지만 typeORM을 사용하면 간단한 코드로 끝낼수 있습니다

// Ashley가 작성하고 상태가 "DONE"인 task들을 가져온다.

// TypeORM
const tasks = await Task.find({ status: 'DONE', user: 'Ashley' })

// Pure JavaScript
let tasks;
db.query('SELECT * FROM tasks WHERE status = "DONE" AND user = "Ashley"', (err, result) => {
    if (err) {
        throw new Error("Could not retrieve tasks");
    }
    tasks = result.rows;
})

ORM의 장점

장점

객체 지향적인 코드

  • ORM을 사용하면 SQL문이 아닌 클래스의 메소드를 이용해 데이터베이스를 조작할 수 있습니다
    • 따라서 개발자가 객체 모델만 이용해서 프로그래밍을 하는데 집중할 수 있습니다.
  • SQL문을 raw한게 사용했을 때의 선언,할당, 종료 같은 부수적인 코드를 줄일 수 있고, 가독성을 높일 수 있습니다
  • SQL와의 혼재 없이 객체지향적인 접근만 고려하면 되기 때문에 생산성이 증가합니다

재사용, 유지보수, 리팩토링이 용이

  • 객체로 작성되어있기 때문에 재활용이 용이합니다
  • 매핑하는 정보가 명확하기 때문에 ERD에 의존도를 낮출 수 있습니다

DBMS에 대한 종속성을 줄일 수 있다

  • 객체와의 관계를 바탕으로 SQL문을 자동으로 생성해주고, 객체의 자료형 타입을 사용할 수 있기 때문에 RDBMS의 데이터 구조와 객체지향 모델 사이의 간격을 좁힐 수 있습니다
  • DBMS를 교체하는 큰 작업에도 리스크가 적고 비용이 줄어듭니다

단점

ORM은 모든 것을 해결해주지 않는다

  • ORM은 매우 편리하지만 복잡성이 커질수록 난이도가 올라갑니다
  • 설계를 신중하게 하지 않을 시 속도 저하 및 일관성을 무너뜨리는 문제를 발생시킵니다
  • 일부 SQL문은 속도를 위해 별도의 튜닝이 필요한데, 이럴때는 결국 SQL문을 써야합니다

객체-관계 간의 태생적인 불일치는 해결해주지 않는다

  • 데이터베이스에 있는 테이블 수보다 많은 클래스를 가진 모델이 생길 수 있음
  • RDBMS는 OOP의 특징인 상속 개념이 없음
  • RDBMS는 기본키를 통해 동일성을 제어하는 반면, OOP에서는 경우의 수가 많음
  • RDBMS는 참조 방식이 방향성이 없는 외래키(FK)이지만 OOP에서는 방향성이 있는 참조를 사용함
  • 근본적인 탐색 방법이 다름

따라서 ORM에 너무 의존한다면 이런 단점들에 의해 발생하는 문제들을 해결하는 방법을 터득하지 못할 확률이 높습니다
그래서 SQL 공부를 꾸준히 해야하지 않을까 합니다

Mapping이란?

매핑이란 해당 값이 다른 값을 가리키도록 하는 것
다른 데이터 셋과 대응 관계를 가지고 있는 일련의 데이터 셋을 지칭합니다

TypeORM의 특징

  • Node.js 에서 작동하며 항상 최신 JavaScript,TypeScript 기능을 지원합니다
  • 엔티티, 레파지토리 등 다양한 기능을 지원합니다

Active Record 패턴과 Data Mapper 패턴을 지원합니다

  • Active Record 패턴
    • 모델 그 자체에 쿼리 메소드를 정의하고 사용합니다
@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column()
  isActive: boolean;

  static findByName(firstName: string, lastName: string) {
    return this.createQueryBuilder('user')
      .where('user.firstName = :firstName', { firstName })
      .andWhere('user.lastName = :lastName', { lastName })
      .getMany();
  }
}

// 분리

// example how to save AR entity
const user = new User();
user.firstName = 'Timber';
user.lastName = 'Saw';
user.isActive = true;
await user.save();

// example how to remove AR entity
await user.remove();

// example how to load AR entities
const users = await User.find({ skip: 2, take: 5 });
const newUsers = await User.find({ isActive: true });
const timber = await User.findOne({ firstName: 'Timber', lastName: 'Saw' });
const timber = await User.findByName('Timber', 'Saw');


  • Data Mapper 패턴
    • 분리된 클래스에 쿼리 메소드를 정의하는 방식입니다
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column()
  isActive: boolean;
}

// 분리
import { EntityRepository, Repository } from 'typeorm';
import { User } from '../entity/User';

@EntityRepository()
export class UserRepository extends Repository<User> {
  findByName(firstName: string, lastName: string) {
    return this.createQueryBuilder('user')
      .where('user.firstName = :firstName', { firstName })
      .andWhere('user.lastName = :lastName', { lastName })
      .getMany();
  }
}

// 분리

const userRepository = connection.getRepository(User);

// example how to save DM entity
const user = new User();
user.firstName = 'Timber';
user.lastName = 'Saw';
user.isActive = true;
await userRepository.save(user);

// example how to remove DM entity
await userRepository.remove(user);

// example how to load DM entities
const users = await userRepository.find({ skip: 2, take: 5 });
const newUsers = await userRepository.find({ isActive: true });
const timber = await userRepository.findOne({
  firstName: 'Timber',
  lastName: 'Saw',
});
  • 저는 유지 보수에 용이한 Data-Mapper 방식으로 주로 코드를 작성해왔습니다

TypeORM을 사용한 이유

  • 제가 주로 사용한 Nest.js 프레임워크는 객체지향적으로 설계하도록 되어있습니다
    • nestjs에서 공식적으로 지원하는 ORM입니다
  • 따라서 nestjs로 구성한 서버와 RDB 서버간 패러다임의 불일치를 줄이기 위해 사용했습니다
  • 쓴이가 메인 도메인언어로 사용하고 있는 타입스크립트와 자바스크립트(ES5, ES6, ES7, ES8)와 함께 사용할 수 있습니다
  • 경험해보진 않았지만 시퀄라이즈ORM보다 typescript과의 융합이 잘 된다고 합니다
  • 강력한 쿼리빌더를 지원하고 실제로 활용해보면서 편하다는 것을 느꼈습니다
  • typescript를 다른 ORM보다 강력하게 지원한다는 점
  • 시장에 흐름에 의해 많이 사용되고 있다고 느꼈습니다
    • 주간 다운로드 수가 40만건이 넘으면서 지속적으로 증가되고 있는 추세입니다.

사용하면서 느낀 단점

  • 버전에 따라 사용하는 클래스가 달라지는 걸 보고, 확실히 완성되지는 않은... 개발 중인 솔루션이라는 느낌을 많이 받았습니다
  • 유닛 테스트 할때 모킹을 해보면서 느낀 점인데, 쿼리빌더나 쿼리러너가 연속될때 테스트 코드가 지저분해지는 것 같긴합니다 사용할때는 편하지만 TDD를 할때는 귀찮아지는 느낌?

Nest.js 상에서의 TypeORM 초기 세팅

TypeOrmModule.forRoot({
      type: 'mysql',
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_DATABASE,
      entities: [__dirname + '/apis/**/*.entity.*'],
      synchronize: true,
      logging: true,
      retryAttempts: 30,
      retryDelay: 5000,
      timezone: 'Z',
    }),

typeORM을 설치할때 옵션을 보통 위와 같이 설정해서 typeORM 모듈을 app.module.ts의 import에 위치시킵니다

  • synchronize : 애플리케이션에 작동할 때마다 DB Schema가 자동으로 만들어지게 할 것인가를 설정

  • logging: 로깅을 할 것인가 하지 않을 것인가를 설정

  • entities: 연결에 사용되며 로드되는 entity. 설정한 entity class와 로드하기 위한 directory path를 허용

  • timezone: ORM의 타임존을 설정합니다. 저는 db를 도커로 올리는 과정에서 타임존을 한국시간으로 설정해놓았기 때문에, 저렇게 Z만 입력해서 ORM이 database 자체의 타임존을 따라가도록 했습니다

  • 엔티티나 레포지토리, 쿼리빌더, 쿼리러너 등 다양한 기능들이 있지만 일단 초기 설정 값만 포스팅하고 나머지는 따로 포스팅하겠습니다

정리

ORM이란?

  • Object-Relational Mapping의 약자로 OOP와 RDB를 이어주는 솔루션을 말합니다
  • OOP와 RDB 간의 패러다임 불일치를 해소하기 위해 사용합니다

ORM의 장점

  • 객체 언어만 사용해서 데이터베이스를 조작할 수 있기 때문에 객체 프로그래밍에 집중할 수 있습니다
  • OOP언어를 통해 SQL을 자동으로 생성해주기 때문에 부수적인 코드를 줄이고 코드의 가독성을 높일 수 있습니다
  • 재사용, 유지보수에 용이하며 작성하는 정보가 명확하기 때문에 ERD에 의존도가 줄어듭니다
  • DBMS에 대한 의존도를 낮출 수 있고 쉽게 다른 RDBMS로 마이그레이션할 수 있습니다

ORM의 단점

  • 쉽지만 복잡성이 커질수록 난이도가 올라갑니다
  • 설계를 신중하게 하지 않을시 속도 저하와 일관성이 무너지는 이슈가 있습니다
  • 객체-관계간의 태생적인 불일치를 해결해주지 않기 때문에 결국 SQL문을 써야하는 순간이 있습니다
  • 따라서 ORM에 너무 의존한다면 이런 단점들에 의해 발생하는 문제들을 해결하는 방법을 터득하지 못할 확률이 높습니다. 그래서 SQL 공부를 꾸준히 해야하지 않을까 합니다

TypeORM을 사용한 이유

  • nestjs 공식 지원 ORM입니다
  • 편리하게 RDB의 데이터를 관리하기 위해 사용했습니다
  • 타입스크립트와 자바스크립트를 지원하며 타입스크립트를 다른 ORM보다 강력하게 지원합니다
  • 시장에 흐름에 의해 많이 사용되고 있다고 느꼈습니다
profile
개발 공부중

0개의 댓글