Search

TypeORM snake_case 도입 및 camelCase 회귀 결정 기록

생성일
2025/05/03 03:45
태그
AI 요약
날짜
상위 항목
하위 항목

도입 배경

PostgreSQL에서 컬럼/테이블명에 camelCase를 사용하면 내부적으로 "큰따옴표"로 감싸지지 않으면 에러 발생
현재 코드에서 chatChannelId, createdAtcamelCase 컬럼명을 그대로 사용하고 있었고, 그에 따라 쿼리 작성 시 "channelChatLog"."chatChannelId" 같은 따옴표를 반드시 사용해야 하는 상황 발생
SELECT c."channelName", ccl.message, ccl.nickname, ccl."createdAt", ccl."userIdHash", ccl."chatType" FROM "channelChatLog" ccl LEFT JOIN "channel" c ON c.id = ccl."channelId" ORDER BY ccl."createdAt" DESC;
SQL
복사

문제점 정리

SQL 쿼리 작성 시 항상 큰따옴표를 써야 해서 불편
쿼리 툴에서 자동완성/탭 완성 제대로 안 됨
일부 raw query 실행 시 대소문자 이슈로 휴먼에러 빈번하게 발생
TypeORM 마이그레이션이나 View, Index, Function 등 연동 시 혼란 ( idx_channelChatLog_chatChannelId 와 같은 혼란스러운 네이밍… )

해결 전략

TypeORM의 @Column({ name: '...' }) 매핑 방식으로 DB는 snake_case, 코드는 camelCase 유지
→ 단점: 매번 name 명시가 번거로움이 있어 Naming 전략 도입
커스텀 NamingStrategy 도입SnakeNamingStrategy
import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm'; import { snakeCase } from 'typeorm/util/StringUtils'; export class SnakeNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface { tableName(className: string, customName: string): string { return customName || snakeCase(className); } columnName(propertyName: string, customName: string, embeddedPrefixes: string[]): string { return snakeCase(embeddedPrefixes.concat(customName || propertyName).join('_')); } relationName(propertyName: string): string { return snakeCase(propertyName); } joinColumnName(relationName: string, referencedColumnName: string): string { return snakeCase(`${relationName}_${referencedColumnName}`); } joinTableName(firstTableName: string, secondTableName: string, firstPropertyName: string): string { return snakeCase(`${firstTableName}_${firstPropertyName}_${secondTableName}`); } joinTableColumnName(tableName: string, propertyName: string, columnName: string): string { return snakeCase(`${tableName}_${columnName || propertyName}`); } classTableInheritanceParentColumnName(parentTableName: string, parentTableIdPropertyName: string): string { return snakeCase(`${parentTableName}_${parentTableIdPropertyName}`); } }
TypeScript
복사

전환 이후 확인된 문제점

1. getRawMany() 결과는 snake_case로 반환됨

TypeORM의 getRawMany()는 DB 결과를 있는 그대로 반환
camelCase로 자동 매핑되지 않음
실무에서는 getRawMany() 또는 native query 사용 빈도가 매우 높음

2. camelCase 기반 DTO 및 비즈니스 로직과 불일치

DTO/서비스 레이어는 camelCase를 전제로 설계되어 있음
snake → camel 변환 시 매핑 유틸이 필요하거나 명시적 alias 지정 필요
alias로 대응 가능하나, 쿼리마다 반복되면 유지보수 부담 증가

3. 성능 손실 및 생산성 저하

snake → camel 변환 유틸 도입 시 추가 연산 발생 ( snakeToCamel, CamelToSnake 등.. )
join/subquery에서 alias 수동 지정은 비효율적
결과적으로 ORM의 이점을 상쇄하게 됨

4. 프로젝트 특성상 SQL 주도 개발이 많음

Whisper, LLM 분석 결과 저장, 실시간 데이터 처리, 파티셔닝 등
복잡한 분석 및 통계 로직은 TypeORM으로 커버 불가
직접 쿼리 작성이 많아질수록 snake_case로 인한 쿼리 불일치가 더 문제됨

결론

snake_case 도입은 철회하고 camelCase 유지
개발자로서 DB 네이밍 컨벤션(관례긴 하지만)을 지켰다는 만족감 을 챙기기 위해서라기엔 너무 리스크가 컸다.
레거시를 청산하고 생산성을 해치면서까지 해야 될 이유가 있냐라고 묻는다면 없다 라고 단언하겠다.