본문 바로가기

FrontEnd

[TypeOrm]ORM을 프로젝트에 도입할 때 주의할점

반응형

ORM에 대한 찬반양론

JPA와 TypeORM이 꽤 많이 쓰이는 것으로 인식되고 있지만, ORM의 효용성 여부는 아직까지도 실무자들 사이에 큰 논란이 되고 있다.

https://martinfowler.com/bliki/OrmHate.html

 

bliki: OrmHate

Object-Relational Mappers get a lot of hate from people who misjudge the complexities of the task.

martinfowler.com

https://www.reddit.com/r/golang/comments/t08oox/are_orms_considered_an_antipattern_in_go/

ORM은 매우 구체적 사항이 누출된 추상화입니다.
사소하지 않은 일에 대해서도 SQL의 전문가이자 ORM의 전문가여야 합니다.
나는 너무 많은 똑똑한 사람들이 ORM 도구를 사용하여 SQL로 5분 짜리 작업이 되었을 작업을 수행하려고 씨름하는 것을 보았습니다.

위와 같이 ORM은 쓸데없이 시니어 프로그래머를 부르는 도구로 인식되고 있다.

ORM의 장점

기술면접에서 ORM의 장점을 쿼리를 대신 짜준다로 답변하면 아마 탈락할 것이다.

ORM의 장점은 Persistent Layer를 추상화하여, 애플리케이션 레벨에서 DBMS 종속성을 제거할 수 있다는 것이다.

나머지 장점은 덤이라 생각한다. 그리고 ORM의 복잡한 애노테이션이나 데코레이터가 없어도 가볍게 쓸 수 있는 방법은 있다.

ORM의 단점

복잡한 쿼리를 작성하는 데는 적합하지 않다. (JPQL or QueryDSL 활용)

생각했던 대로 쿼리가 나가지 않는다. (n+1 문제)

ORM과 DBMS 둘 다 높은 러닝 커브가 있다. 즉 학습 부하가 증가한다.

DBMS에 대한 전문지식은 BE 엔지니어로서 반드시 필요한 것이다.

ORM에 대한 지식은 라이브러리 Specific하고, 안쓰는 프로젝트도 많아서 필수적인 것은 아니다.

ORM이 꼭 필요한가?

ORM, 혹은 SQL query builder는 DBMS 추상화를 위해 존재하는 것이 좋다고 생각한다.

 

ORM을 객체 지향적으로 활용하지 않더라도, DBMS에 저장된 데이터 형태과, 애플리케이션에 로드할 데이터 모양은 다르기 때문에,

매퍼 형태로 존재하는것이 좋다. (보안 사항 등)

(영속 모델과 도메인 모델은 분리해야 한다.)

 

또한, Presentation Layer를 미리 추상화해 두는것은 분명 장점이다.

(실무에서 DBMS를 MySQL에서 PostgreSQL로 변경하라는 권고 사항을 들은 적이 있는데,

전부 raw sql로 작성되어 있어서 간단해보이지 않아 일단 보류했다.

물론 DBMS 자체를 마이그레이션 하는것도 간단하지는 않다.

DBMS를 사용하는 도구가 애플리케이션만 있는게 아닐 수 있다.)

 

문제 해결 : 성급한 추상화의 문제

개인적으로 액티브 레코드 패턴은 안티패턴이라 생각한다.

도메인 모델과 repository 레이어는 분리해야 한다.

 

아래는 TypeORM의 예제다

const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.age = 25
await user.save()

const allUsers = await User.find()
const firstUser = await User.findOneBy({
    id: 1,
})
const timber = await User.findOneBy({
    firstName: "Timber",
    lastName: "Saw"
})

await timber.remove()

User 객체가 findOneBy를 갖고있다는 것에 대해 좀 생각해 봐야 한다.

이는 DMBS에서 해당 정보를 어떻게 갖고오는지(HowToDo)가 누출된 것이다.

 

반대로 레포지토리 레이어를 만들어, sql과 가장 간단한 문법을 추상화 하는 것은 어떤가? 아래는 knex.js를 사용한 예제다.

interface User {
  id: number;
  name: string;
  age: number;
}

function getUserById(id:number){
     return knex('users')
      .where('id',id)
      .first(); // Resolves to any
}

또한, JPA, JPQL, TypeORM, MicroORM 등 ORM 자체도 하나의 인프라스트럭쳐 기술이다.

특히 js 진영의 경우 JPA처럼 완전한 인터페이스가 존재한다고 보기 애매하다.

이를 위해 nest.js가 존재하지만... java처럼 닥치고 스프링인 상황이 아니기 떄문에, 생각보다 ORM은 교체하기가 어렵다.

 

따라서 Repository 레이어룰 인터페이스로 추상화하여 DIP를 적용하는 수밖에 없다.

(나는 꼭 nest.js를 사용해야 한다고 생각하지 않는다. 오히려 함수형 패러다임 측면에선 프레임워크가 불필요할 수도 있다.)

문제 해결 : n+1 문제

https://stackoverflow.com/questions/68981877/how-do-i-cause-n-1-problems-with-nestjs-typeorm

 

How do I cause n + 1 problems with NestJs TypeOrm?

I was studying TypeOrm and I'm trying to create an N+1 problem, but it's not happening properly. Company and employee have a 1:N relationship. Could you tell me why N + 1 is not causing any problem...

stackoverflow.com

이는 영속성의 단위에 대한 이해도가 부족하여 발생하는 문제이다.

도메인 주도 설계에는 이를 해결하기 위한 단위인 Aggregate가 존재한다.

즉, 쿼리는 aggregate단위로 CRUD를 수행해야 한다.

aggregate는 트랜잭션의 원자성 단위다.

따라서 for문 안에서 findBy...를 하는 것은 애초에 잘못된 행위이다.

  • Aggregate는 도메인의 일관성을 보장하고 데이터 트랜잭션의 원자성 단위로 사용되며, 단일 구성 요소로 처리되는 관련된 Entity의 모음입니다. 다른 Entity는 "Root"로 알려진 Aggregate의 "최상위" 구성원 ID인 식별자로만 Aggregate를 참조해야 합니다.

https://itchallenger.tistory.com/418

 

타입으로 도메인 모델링하기 with F# - Aggregate (집합체)

https://pragprog.com/titles/swdddf/domain-modeling-made-functional/ Domain Modeling Made Functional Use domain-driven design to effectively model your business domain, and implement that model with..

itchallenger.tistory.com

대부분의 ORM은 n+1 문제를 해결하기 위한 단위를 설정하는 기능을 지원한다.

TypeORM은 relation 설정에 eager를 true로 주는 것으로 해결한다만,

때때로 꼭 모든 테이블을 조인해서 가져올 필요가 없다는 점이 단점이다.

JPA는 EntityGraph 등의 기능도 지원하는데 말이다.

해당 문제는 아래 문단의 쿼리 빌더를 사용하면 해결된다.

쿼리빌더는 해당 쿼리를 그대로 DBMS에 날리기 때문에, eager, lazy 따위는 생각하지 않아도 된다.

문제 해결 : 복잡한 쿼리 문제

https://itchallenger.tistory.com/230?category=1057505 

 

TypeORM 스터디 : QueryBuilder 1편 - CRUD 기본

TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova a..

itchallenger.tistory.com

Java가 QueryDSL과 JOOQ등을 사용하듯,

타입스크립트의 경우 TypeORM의 QueryBuilder기능을 사용한다.

한마디로 SQL을 추상화하는 것이다.

 

SQL을 ANSI 표준 만으로 짜는건 어떠냐고 질문할 수도 있겠다. 그것도 괜찮다.

모든 DBMS에서 호환된다는 가정이 있다면 말이다.

하지만, 대부분의 SQL Builder 라이브러리가 이와 같은 Heavy Lifting(추상화)를 해주기 때문에,

코드 레벨에서는 그냥 SQL Builder를 가져다 쓰는게 좋다고 생각한다. 인텔리센스 지원도 받고 말이다.

 

문제는 TypeORM의 QueryBuilder가 생각보다 기능이 빈약하다는 점이다.

TypeORM인데 타입 세이프티가 없다. 

그래서 나는 개인적으론, TypeORM을 추천하지 않는다.

Prisma도 비추한다. 쓸데없이 알 내용이 너무 많다고 생각한다. 그래서 typeorm을 못이기는것 같다.

(해당 라이브러리 specific한 내용이 넘많음~)

 

물론 나는 FE엔지니어 이기에 여러분이 걸러들어야 할 내용들도 있다.

npm trend 분석

 

TypeORM의 대체품

https://vincit.github.io/objection.js/

 

Objection.js

 

vincit.github.io

 

날쿼리만 추상하는 방법. 의외로 상당히 많이 쓰임.

https://github.com/knex/knex

 

GitHub - knex/knex: A query builder for PostgreSQL, MySQL, CockroachDB, SQL Server, SQLite3 and Oracle, designed to be flexible,

A query builder for PostgreSQL, MySQL, CockroachDB, SQL Server, SQLite3 and Oracle, designed to be flexible, portable, and fun to use. - GitHub - knex/knex: A query builder for PostgreSQL, MySQL, C...

github.com

 

꼭 TypeORM을 써야겠으면 아래 라이브러리도 Check-Out

https://github.com/samchon/safe-typeorm

 

GitHub - samchon/safe-typeorm: TypeORM helper library enhancing safety in the compilation level

TypeORM helper library enhancing safety in the compilation level - GitHub - samchon/safe-typeorm: TypeORM helper library enhancing safety in the compilation level

github.com

 

반응형