08 — 이론 & 대안 (Theory & Alternatives)
이 챕터가 답하는 질문: TypeORM은 어떤 종류의 도구이고, Prisma·Drizzle·MikroORM·Sequelize와 비교했을 때 언제 옳은가? 그리고 어디서 손해를 보는가? 한 줄 답 (Pyramid Top): “Prisma는 스키마 → 클라이언트 코드젠, Drizzle은 SQL DSL, TypeORM은 데코레이터. 셋은 서로 다른 트레이드오프를 산다 — 어느 손해를 견딜 수 있느냐가 결정의 본질이다.”
한 문장 답 (Pyramid Top)
*“어느 ORM이 좋은가”*는 항상 잘못된 질문이다 — 네 가지 도구는 서로 다른 축에서 다른 손해를 감수한다. TypeORM은 데코레이터 한 줄로 컴파일 타임 타입과 런타임 메타데이터를 동시에 얻지만 — 타입 정확도는 부분적이다. Prisma는 스키마 → 코드젠으로 완전한 타입을 얻지만 — 별도 빌드 스텝과 Rust 엔진 바이너리를 산다. Drizzle은 SQL DSL로 런타임 제로 코스트를 얻지만 — ORM 추상화가 거의 없어 SQL을 직접 알아야 한다. MikroORM은 진짜 Unit of Work + Identity Map을 구현했지만 — 학습 곡선과 복잡도가 비싸다. 이 챕터는 그 손해의 축을 하나씩 그어준다.
챕터 지도 (Mermaid)
Why — 왜 이 챕터가 마지막 직전에 오나
00~07을 따라오면 TypeORM이 어떻게 동작하는지는 충분히 다뤘다. 그러나 production에 올리기 직전, 모든 팀이 같은 질문을 다시 만난다.
“우리 케이스에 진짜 TypeORM이 맞나? Prisma가 더 빠르다던데? Drizzle은 더 가볍다던데? MikroORM은 더 제대로라던데?”
이 질문에 *“TypeORM이 무조건 좋다”*고 답하는 자료는 거의 영업 자료다. 현실의 답은 항상 **“무엇과 무엇을 어떤 축에서 비교하느냐”**부터 정리해야 풀린다. 이 챕터의 8개 문서는 그 축을 하나씩 그어준다.
- 왜 ORM 자체가 존재하는가:
01— impedance mismatch와 Martin Fowler의 정의 - 흔한 비교:
02(Prisma) ·03(Drizzle) ·04(MikroORM) ·05(Sequelize) - 피해야 할 자리:
06— 강한 타입·ESM·OLAP·raw SQL 우선 - 결정 매트릭스:
07— 4축 × 4도구
How — 어떻게 읽나
세 가지 경로가 있다.
| 독자 | 추천 순서 |
|---|---|
| ”TypeORM을 쓸지 말지 고민 중” | 06 → 07 → 02 (현실 적합성 먼저) |
| “Prisma·Drizzle 중 갈아탈지 비교” | 02 → 03 → 07 (구체 비교 + 매트릭스) |
| “ORM 패턴 자체를 이해하고 싶다” | 01 → 04 → 05 (이론 + 친척 + 역사) |
| # | 파일 | 읽는 데 | 핵심 키워드 |
|---|---|---|---|
| 01 | 01-orm-as-a-pattern.mdx | 12분 | impedance mismatch · Martin Fowler · DataMapper · Unit of Work |
| 02 | 02-typeorm-vs-prisma.mdx | 14분 | 데코레이터 · 코드젠 · 타입 정확도 · Rust 엔진 |
| 03 | 03-typeorm-vs-drizzle.mdx | 12분 | SQL DSL · 런타임 제로 코스트 · 메타데이터 |
| 04 | 04-typeorm-vs-mikroorm.mdx | 10분 | Identity Map · Unit of Work · em.flush · 친척 |
| 05 | 05-typeorm-vs-sequelize.mdx | 10분 | JS 시대 · Model.init · 타입 정확도 · 역사 |
| 06 | 06-when-not-to-use-typeorm.mdx | 12분 | ESM · OLAP · raw SQL · 강타입 정확도 |
| 07 | 07-decision-matrix.mdx | 10분 | 팀 규모 · 타입 요구 · SQL 친밀도 · 마이그레이션 빈도 |
도구 비교 매트릭스 (한 페이지 요약)
| 축 | TypeORM | Prisma | Drizzle | MikroORM | Sequelize |
|---|---|---|---|---|---|
| 모델 정의 | 데코레이터 (TS class) | 자체 DSL (schema.prisma) | TS 객체 (table()) | 데코레이터 (TS class) | Model.init (JS class) |
| 메타데이터 | reflect-metadata (런타임) | 코드젠 (빌드 타임) | TS inference (컴파일 타임) | reflect-metadata | 인스턴스 attribute |
| 패턴 | DataMapper (느슨) | DataMapper (코드젠) | SQL DSL (ORM 아님) | DataMapper (정통) | ActiveRecord |
| Unit of Work | 부분적 (cascade) | 없음 (명시적 호출) | 없음 | 완전 (em.flush) | 없음 |
| Identity Map | 부분적 | 없음 | 없음 | 완전 | 없음 |
| 타입 정확도 | 부분적 | 완전 | 완전 | 부분적 | 낮음 |
| 마이그레이션 | migration:generate (CLI) | prisma migrate (DSL diff) | drizzle-kit generate | migration:create | umzug 통합 |
| raw SQL escape hatch | manager.query | $queryRaw | 기본이 SQL | em.getConnection().execute | sequelize.query |
| ESM 친화도 | 낮음 (CJS 친화) | 중간 | 높음 | 중간 | 낮음 |
| 런타임 오버헤드 | 메타데이터 + 변환 비용 | 엔진 IPC | 거의 0 | 메타데이터 + UoW | 인스턴스 변환 |
| NestJS 통합 | @nestjs/typeorm (1급) | nestjs-prisma (커뮤니티) | 직접 모듈 | @mikro-orm/nestjs | @nestjs/sequelize |
| GitHub stars (2024) | ~33k | ~37k | ~20k | ~8k | ~29k |
| 최초 release | 2016 | 2019 (v1) | 2022 | 2018 | 2010 |
| 잘 맞는 자리 | 데코레이터 친숙·NestJS·OOP 도메인 | 강한 타입 요구·풀스택 TS | SQL 친숙·서버리스·엣지 | DDD·복잡 도메인·UoW 필요 | 레거시 JS 코드베이스 |
| 안 맞는 자리 | 완전 타입 요구·ESM 우선·OLAP | Rust 바이너리 거부·DSL 거부 | ORM 추상 필요·OOP 도메인 | 학습 비용 부담 | 새 TS 프로젝트 |
한 회사가 여러 도구를 동시에 쓰는 일도 흔하다 — 어느 자리에 어느 도구를 쓰느냐가 진짜 결정.
What — 한 페이지 요약 (모든 문서의 핵심 한 줄)
| 문서 | 한 줄 결론 |
|---|---|
| 01 | ORM은 객체와 관계형의 impedance mismatch를 메우려는 패턴이고 — TypeORM은 그중 DataMapper의 느슨한 구현이다. |
| 02 | Prisma는 스키마 → 코드젠으로 완전한 타입을 얻고, TypeORM은 데코레이터로 부분 타입과 유연성을 얻는다. |
| 03 | Drizzle은 ORM이 아니라 SQL DSL이다 — 런타임에 메타가 0이고, TypeORM은 런타임 메타로 모든 마법을 한다. |
| 04 | MikroORM은 진짜 Unit of Work + Identity Map을 구현했다 — TypeORM이 가장 닮고 싶었던 모습이지만 못 한 부분이다. |
| 05 | Sequelize는 JavaScript 시대의 ORM이고 TypeORM은 TypeScript 시대의 ORM이다 — 타입 지원 격차가 근본적이다. |
| 06 | TypeORM을 쓰지 말 자리: 완전 타입 정확도가 매출에 직결될 때, ESM 우선 스택, raw SQL이 즉시 필요한 OLAP, 데코레이터 spec 변동성에 민감할 때. |
| 07 | 4축(팀 규모 · 타입 요구 · SQL 친밀도 · 마이그레이션 빈도) × 4도구 매트릭스로 한 페이지 결정. |
What-if — 이 챕터를 건너뛰면
- “TypeORM은 무조건 좋다”고 믿으면: 강한 타입이 필요한 결제·핀테크 도메인에서 부분 타입의 빈틈이 production 버그로 돌아온다. 6개월 뒤 Prisma로 이주.
- “ORM은 다 똑같다”고 믿으면: MikroORM의 Unit of Work가 풀어주는 N+1과 cascade 문제를 TypeORM에서 수동으로 다 짜다 무너진다.
- “Drizzle은 ORM이다”라고 믿으면: ORM의 추상 레이어를 기대하다 그냥 SQL DSL임을 깨닫고 — 자기 도메인 모델을 맨손으로 다시 만들어야 한다.
- “Sequelize도 충분히 type-safe하다”고 믿으면:
Model.findAll<...>의 제네릭 누수가 production에서 any를 통과시켜 런타임 에러로 돌아온다.
Insight — 한 단락 이야기
“네 가지 ORM, 네 가지 다른 손해”
2016년 TypeORM이 등장했을 때 — TypeScript 진영의 ORM은 사실상 Sequelize 하나였다. 데코레이터 + reflect-metadata라는 TC39 Stage 1 실험을 ORM에 적용한 시도는 대담했고 시기적절했다. NestJS가 같은 패턴을 받아들이면서 TypeORM은 사실상 표준이 되었다.
그러나 2019년 Prisma가 *“메타데이터를 런타임에 두지 말고, 빌드 타임에 코드를 생성하자”*라는 정반대 선택을 하면서 — 완전한 타입 정확도라는 TypeORM이 끝까지 닿을 수 없던 자리를 차지했다.
2022년 Drizzle은 또 다른 길을 보였다 — “ORM 추상을 버리고, SQL DSL을 TypeScript로 표현하자”. 런타임 메타데이터가 0이라 서버리스·엣지에서 cold start가 사라졌다.
그리고 그 사이, MikroORM은 조용히 TypeORM이 못 한 Unit of Work를 구현했다 — DDD 진영의 사람들이 진짜 ORM이라 부르는 그 자리.
네 가지 도구가 네 가지 다른 손해를 산다 — TypeORM의 손해는 완전 타입의 절반이지만, 그 대신 데코레이터 한 줄의 익숙함을 얻는다. 이게 무거운지 가벼운지는 팀의 결제 정확도가 매출에 직결되는지에 달렸다.
한 단락 요약
ORM은 객체-관계 impedance mismatch를 메우는 패턴(
01)이고 — 그 안에서 TypeORM은 데코레이터, Prisma는 코드젠(02), Drizzle은 SQL DSL(03), MikroORM은 정통 DataMapper(04), Sequelize는 JS 시대 유산(05)이라는 서로 다른 손해를 산다. 피해야 할 자리는06이 정리하고, 4축 결정 매트릭스는07이 그린다. 이 챕터를 끝내면 *“TypeORM이 좋은가”*가 아니라 *“이 자리에 TypeORM의 손해가 견딜만한가”*를 묻게 된다. 다음 챕터(09-real-world-cases)는 이 매트릭스를 실제 회사들이 어떻게 적용했는지를 본다.