codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
노트›backend

TypeORM 과 읽기 전용 엔티티

2026-04-28 게시· 2026-05-18 갱신·0회 조회

TypeORM 과 읽기 전용 엔티티

TypeORM 은 한때 Node 진영의 대표적 ORM 이었고 NestJS 와 함께 널리 쓰였습니다. 새 ORM 들이 등장하면서 위치가 다소 바뀌었지만 여전히 많은 코드베이스에서 운영됩니다.

1. TypeORM 에 대한 이야기

TypeORM 은 2016 년경 첫 공개된 TypeScript/JavaScript ORM 으로 알려져 있습니다. PostgreSQL · MySQL · MariaDB · SQLite · MS SQL Server · Oracle 등 다양한 RDB 와 일부 NoSQL (MongoDB) 을 지원합니다.

라이브러리 첫 등장 성격
TypeORM 2016 데코레이터 기반. AR · DM 양쪽 지원.
Sequelize 2010 가장 오래된 Node ORM. JS 우선.
MikroORM 2018 TS 우선. Identity Map · Unit of Work.
Prisma 2019 (Prisma 1) → 2020 (현재 Prisma) 스키마 파일 + 생성된 클라이언트.
Drizzle ORM 2022 TS 스키마 코드. SQL 에 가까운 빌더.
Kysely 2022 type-safe 쿼리 빌더 (엄밀히는 ORM 아님).

2. Active Record vs Data Mapper

Martin Fowler 의 Patterns of Enterprise Application Architecture (2002) 가 정리한 두 패턴입니다.

  • Active Record — 엔티티 객체가 자기 자신을 저장합니다. user.save().
  • Data Mapper — 별도 매퍼·리포지토리가 엔티티를 영속화합니다. repo.save(user).

TypeORM 은 둘 다 제공합니다.

// Active Record
@Entity()
class User extends BaseEntity {
  @PrimaryGeneratedColumn() id!: number
  @Column() email!: string
}
const u = User.create({ email: 'a@b' })
await u.save()

// Data Mapper
const repo = dataSource.getRepository(User)
await repo.save(repo.create({ email: 'a@b' }))

서비스 계층 분리·테스트 용이성이 강조되는 환경에서는 Data Mapper 쪽이 자주 선택됩니다.

3. 엔티티 → 읽기 전용 SSOT 패턴

TypeORM 은 엔티티 메타데이터로 스키마를 동기화 (synchronize: true) 하거나 마이그레이션을 생성 (migration:generate) 할 수 있습니다. 운영에서는 두 가지 방향이 흔히 갈립니다.

① TypeORM 이 스키마의 진실 — 엔티티가 SSOT, 마이그레이션은 자동 생성
② SQL 이 스키마의 진실 — 엔티티는 매핑만, 스키마 변경은 SQL 파일에서

(2) 의 경우 엔티티 데코레이터는 컬럼 이름·타입·관계만 표현하고 synchronize: true 는 절대 켜지 않습니다. 운영 DB 의 변경은 별도 SQL 또는 마이그레이션 도구로만 합니다. 장점:

  • DBA · 데이터팀이 SQL 을 직접 다룰 수 있습니다.
  • 자동 생성 마이그레이션의 위험한 변경 (컬럼 삭제·타입 변경) 을 막습니다.
  • 같은 DB 를 다른 언어/서비스가 공유할 때 진실 출처가 한 곳입니다.

단점은 엔티티와 실제 DB 가 어긋날 수 있다는 점입니다. 통합 테스트로 양쪽 일치를 검증하는 절차가 필요합니다.

4. 관계와 N+1

@Entity()
class Order {
  @ManyToOne(() => User) user!: User
}

const orders = await repo.find()
for (const o of orders) {
  console.log(o.user.email)  // lazy load: 매번 SELECT
}

이 패턴이 전형적인 N+1 문제입니다. 한 번의 메인 쿼리 + N 번의 보조 쿼리. 해결은 명시적 join.

const orders = await repo.find({ relations: { user: true } })
// 또는 query builder
const orders = await repo.createQueryBuilder('o')
  .leftJoinAndSelect('o.user', 'u')
  .getMany()

또는 DataLoader 같은 배치 로더 패턴을 도입합니다 (GraphQL 환경에서 흔함).

5. Prisma · Drizzle · MikroORM 과의 비교

Prisma — schema.prisma 가 SSOT, 클라이언트가 코드 생성. 타입 추론이 강하고 마이그레이션이 잘 정리돼 있습니다. 런타임은 쿼리 엔진(Rust 바이너리) 을 거치는 구조였고 최근에는 엔진을 줄이는 방향이 진행 중입니다.

Drizzle — SQL 에 가까운 빌더. 스키마는 TS 코드. 런타임 오버헤드가 작고 타입 추론이 강합니다.

MikroORM — Identity Map · Unit of Work 같은 엔터프라이즈 패턴을 정확히 구현합니다. 엔티티 변경을 추적해 한 번에 flush.

Sequelize — 오래된 만큼 안정적이지만 타입 추론은 위 셋에 비해 약하다는 평이 있습니다.

선택은 팀의 SQL 친밀도, 마이그레이션 방식, 타입 안정성의 필요 수준에 달려 있습니다.

6. DataSource 와 Repository

DataSource 는 연결 풀 + 메타데이터를 담는 객체입니다. 앱 시작 시 한 번 초기화하고 전역에서 참조합니다.

export const ds = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  entities: [User, Order],
  migrations: ['migrations/*.ts'],
  synchronize: false,   // 운영에서는 false
})
await ds.initialize()
const userRepo = ds.getRepository(User)
class UserService {
  constructor(private repo = userRepo) {}
  byEmail(email: string) { return this.repo.findOne({ where: { email } }) }
}

리포지토리 메서드를 서비스 안에 두는 형태와 별도 클래스로 빼는 형태가 모두 가능합니다.

7. 자주 걸리는 자리

synchronize: true 의 운영 적용 — 컬럼이 사라질 수 있습니다. 운영에서는 절대 끕니다.

@JoinColumn 누락 — ManyToOne 관계에서 외래키 컬럼명이 의도와 달라집니다. 명시 권장입니다.

순환 임포트 — 엔티티 간 양방향 관계는 함수형 참조 (() => Other) 로 lazy 로 두면 순환을 회피합니다.

타임존 컬럼 — PostgreSQL 의 timestamptz 와 timestamp 의 차이를 엔티티 데코레이터에 정확히 반영합니다. 잘못 매핑하면 시간이 어긋납니다.

트랜잭션 경계 — 여러 리포지토리를 한 트랜잭션으로 묶으려면 dataSource.transaction(async (manager) => ...) 안에서 매니저 기반 리포지토리를 써야 합니다.

하고픈 말

ORM 은 추상의 가치와 raw SQL 의 명료함 사이의 트레이드오프입니다. 작은 팀은 raw SQL 로 시작하는 게 운영 부담이 작고, 큰 팀이나 도메인이 많아지면 ORM 의 추상 이득이 보입니다. "엔티티는 매핑만, 스키마는 SQL" 패턴이 둘의 절충점에 가깝습니다.

Next

  • crawler-ethics
  • openapi-spec

TypeORM 공식 · TypeORM GitHub · Patterns of Enterprise Application Architecture · Prisma 공식 · Drizzle ORM 공식 · MikroORM 공식 · DataLoader 를 참고합니다.

backend 카테고리의 다른 글

카테고리 전체 보기 →
  • 공공 OpenAPI 는 자체 BFF 로 한 번 감싼다
  • 이메일 발송과 OTP — SMTP
  • 감사로그 — logAdminAction 패턴
  • WebSocket · SSE — 실시간 통신
  • REST API 입문
  • OpenAPI 사양