도입 배경
•
PostgreSQL에서 컬럼/테이블명에 camelCase를 사용하면 내부적으로 "큰따옴표"로 감싸지지 않으면 에러 발생
•
현재 코드에서 chatChannelId, createdAt 등 camelCase 컬럼명을 그대로 사용하고 있었고, 그에 따라 쿼리 작성 시 "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 네이밍 컨벤션(관례긴 하지만)을 지켰다는 만족감 을 챙기기 위해서라기엔 너무 리스크가 컸다.
레거시를 청산하고 생산성을 해치면서까지 해야 될 이유가 있냐라고 묻는다면 없다 라고 단언하겠다.