01 · Entity & 데코레이터
이 챕터가 답하는 질문: TypeORM의
@Entity/@Column한 줄은 런타임에 무엇을 박고, 컴파일 타임에 무엇을 강제하며, DB에는 어떤 DDL을 남기는가? 한 줄 답 (Pyramid Top): “@Entity한 줄이 런타임 메타데이터 + 컴파일 타임 타입 + DDL 생성 세 가지를 동시에 한다 — 이 챕터는 그 한 줄을 6개의 결정으로 분해한다.”
챕터 지도 (Mermaid)
Why — 왜 이 챕터를 먼저 보나
TypeORM 사용자의 첫 1주일 사고 대부분이 이 챕터에서 나온다.
- “왜
@Column() name: string에서 마이그레이션이varchar아닌text로 떨어지지?” → 컬럼 타입 추론이 일부만 동작 → 02-column-types - “왜
@PrimaryGeneratedColumn('uuid')로 했더니 PG에선 되는데 MySQL에선 다른 동작이지?” → 드라이버별 시퀀스가 다름 → 03-primary-keys - “
@DeleteDateColumn박았는데 왜find가 삭제된 row도 가져오지?” → soft delete는 find의 의미를 바꾼다 → 04-special-columns - “왜
@Embedded한 객체가 컬럼 prefix로 펼쳐지지?” → embedded는 값 객체의 표현이지 조인이 아님 → 05-embedded-and-inheritance - “데코레이터를 쓰면 DI/IoC와 충돌한다는데 어떻게 피하지?” →
EntitySchema로 클래스 외부에 메타를 적는다 → 06-entity-schema-alternative
이 챕터는 Entity라는 한 단어 안에 숨어 있는 6개의 결정에 이름을 붙인다.
How — 어떻게 읽나
| # | 문서 | 읽는 데 | 핵심 키워드 |
|---|---|---|---|
| 01 | 01-entity-decorator | 8분 | @Entity, name, schema, database, synchronize |
| 02 | 02-column-types | 12분 | @Column, type, nullable, default, length, unique, comment |
| 03 | 03-primary-keys | 10분 | @PrimaryColumn, @PrimaryGeneratedColumn, increment/uuid/rowid/identity, 복합키 |
| 04 | 04-special-columns | 12분 | @CreateDateColumn, @UpdateDateColumn, @DeleteDateColumn, @VersionColumn |
| 05 | 05-embedded-and-inheritance | 12분 | @Embedded, @TableInheritance, @ChildEntity, STI vs CTI |
| 06 | 06-entity-schema-alternative | 10분 | EntitySchema, schema-first, 데코레이터 회피 |
의존성: 01은 모든 것의 입구. 02·03·04는 컬럼 차원의 결정이라 같은 층에 있다. 05는 02 위에 쌓이는 조합 결정. 06은 대안이라 마지막.
What — 한 페이지 요약
| 문서 | 한 줄 결론 |
|---|---|
| 01 | @Entity는 클래스를 테이블로 등록하는 한 줄이다 — name/schema/database 인자가 어디에 만들지를 결정한다. |
| 02 | @Column 타입은 TypeScript 원시 타입만 자동 추론된다 — Date·enum·decimal·json은 명시해야 한다. |
| 03 | @PrimaryColumn은 수동 PK, @PrimaryGeneratedColumn은 자동 PK — 'increment'/'uuid'/'rowid'/'identity' 4 전략 중 DB와 분산성이 답을 정한다. |
| 04 | @CreateDateColumn·@UpdateDateColumn·@DeleteDateColumn·@VersionColumn 네 줄이 시간·삭제·동시성을 동시에 해결한다 — 그러나 @DeleteDateColumn은 find의 의미를 바꾼다. |
| 05 | @Embedded는 값 객체를 컬럼 prefix로 펼치고, STI는 한 테이블에 type 컬럼, CTI는 부모/자식 테이블 분리. 셋은 다른 트레이드오프를 산다. |
| 06 | EntitySchema는 데코레이터 없이 같은 메타데이터를 적는 길이다 — DI 컨테이너 충돌이나 POJO 도메인 모델 유지에 쓴다. |
What-if — 이 챕터를 건너뛰면
@Column()한 줄을 모르면: TS 타입과 DB 타입의 불일치를 매번 마이그레이션에서 마주친다.- PK 전략을 모르면:
'increment'로 시작했다 수평 분할 시점에 UUID로 갈아엎는다. @DeleteDateColumn의 부작용을 모르면: 운영 데이터가 사라진 줄 알고 다시 insert → unique constraint violation.- Embedded·상속을 모르면: 같은 컬럼 묶음을 5개 테이블에 반복 정의한다.
EntitySchema를 모르면: 도메인 모델이 TypeORM에 강결합되어 클린 아키텍처 시도가 막힌다.
Insight — 한 단락 이야기
“
@Entity한 줄은 사실 3개의 책임을 동시에 진다”2016년 Umed Khudoiberdiev가 TypeORM 첫 커밋을 적을 때 — Java의 JPA, C#의 EF가 영감이었다. 그러나 JPA는
@Entity가 런타임 메타만 박고 DDL은ddl-auto옵션이 별도로 만든다. TypeORM은 그것을 한 데코레이터로 묶었다. 결과: 학습 비용은 낮아졌지만, 세 책임이 한 줄에 모인 결정이 나중에 풀기 어려운 결합이 됐다.synchronize: true가 프로덕션 폭탄이 된 이유,EntitySchema가 대안으로 살아남은 이유 모두 이 한 줄에서 시작한다. 편의는 항상 결합의 비용을 청구한다 — 단지 영수증이 늦게 올 뿐이다.
한 단락 요약
Entity는 런타임 메타 + 컴파일 타입 + DDL 생성의 세 책임을 한 데코레이터로 묶은 TypeORM의 핵심 결정이다. 이 챕터를 끝내면
@Entity({ name: 'users' }) class User { @PrimaryGeneratedColumn('uuid') id: string }한 블록에 들어 있는 6개의 결정(테이블명·PK 전략·UUID 생성 위치·컬럼 타입 추론·인덱스·마이그레이션 영향)을 분리해서 읽을 수 있다. 다음 챕터(02-relations)는 이 엔티티들을 어떻게 잇는가를 다룬다.