개발

바이브 코딩 앞에서 ORM이 다시 귀해진 이유

한때는 “진짜는 SQL 친다” 쪽으로 밀렸던 ORM이, 에이전트가 코드를 뿌리는 지금은 스키마·마이그레이션 덕에 다시 중심에 선다는 이야기. 천대받던 이유, 바이브 코딩이 바꾼 판, 실제 적용 사례, 그리고 도입·운영 시 주의사항.

바이브 코딩 앞에서 ORM이 다시 귀해진 이유

몇 년 전만 해도 면접 자리나 사내 세미나에서 ORM 얘기 나오면 분위기가 묘했습니다. “우리는 복잡해서 ORM 안 쓰고 쿼리 직접 씁니다”가 어깨 펴는 말처럼 들리던 시기가 있었죠. 반대로 ORM을 고집하면 유행 지난 스택이나 성능 모를 사람 딱지를 딸까 봐 괜히 방어적으로 설명하게 됐고요.

지금은 조금 다릅니다. 챗이나 에이전트로 하루 만에 파일이 수십 개 늘어나는 팀이 흔해졌고, 그때 “DB랑 앱이 어디서 만나는지”를 한눈에 고정해 두는 장치가 다시 귀해졌습니다. ORM이라는 이름이 꼭 들어가지 않아도, 스키마 파일 + 생성 클라이언트 + 마이그레이션 궤적 같은 묶음이요.

이 글은 ORM 옹호문도 비판문도 아닙니다. 왜 한동안 밑으로 깔렸다가, 바이브 코딩이 들어오면서 위로 올라왔는지를 제가 현장에서 느낀 대로 풀어 본 겁니다. 아래쪽은 필요한 것만 짧게.

ORM이 끼는 위치

ORM이 원래 하던 일

테이블이랑 객체는 말하는 방식이 다릅니다. ORM은 그 사이를 대신 번역해 주는 층이에요. 잘될 땐 CRUD가 빨라지고, 팀 안에서 패턴이 맞춰집니다. 잘못되면 생성된 SQL이 늘어나는 건 사용자 몰래라서, 나중에 트래픽 타고 나서야 “어쩌다 이렇게 됐지?” 하게 됩니다.

여기까지는 예전이나 지금이나 같습니다. 달라진 건 누가 그 층 위에서 코드를 얼마나 빨리 양산하느냐예요.

한동안 ORM이 천대받던 이유

솔직히 말하면 욕먹을 만한 사례도 많았습니다. 개발자는 세 줄 썼는데 로그에는 SELECT가 수십 줄인 경우. 리뷰어가 SQL을 안 보면 그냥 지나감. 컨퍼런스나 블로그에선 raw SQL·실행 계획 들고 나오는 글이 멋있어 보이고, ORM은 그늘에 가까웠죠. ORM만 믿다 유니크·외래키·인덱스 이야기가 코드 리뷰에서 밀리기도 했고, Hibernate XML이나 TypeORM 데코레이터 지옥처럼 도구 자체가 무겁게 기억된 사람도 많습니다.

그래서 “ORM 없이 쿼리 빌더만”, “마이바티스로 SQL을 손에 쥔다” 쪽이 성숙한 선택처럼 보였던 겁니다. 그 판단이 당시엔 꽤 맞는 경우도 많았어요.

바이브 코딩이 바꾼 것

에이전트는 빠릅니다. 근데 맥락이 얇으면 그럴듯하게 틀립니다. 테이블 이름 하나, 조인 방향 하나만 어긋나도 로컬에선 돌아가는 척하다가 통합 테스트나 프로덕션에서 터지기 십상이죠.

그때 예전에 밉보이던 층이 다른 용도로 빛을 봅니다. 스키마가 파일로 박혀 있으면 모델이 테이블을 추측해서 짜는 일이 줄고, schema.prisma 한 장이 레포 안에서 공용 지도 역할을 하죠. 마이그레이션이 커밋으로 남으면 “DB가 왜 이렇게 됐지?”가 diff로 설명됩니다. 생성된 클라이언트·타입이 있으면 에이전트가 지어낸 메서드 이름이 빌드에서 걸리기도 쉽고요.

요즘 회의실에서는 **“ORM 쓰자”**라기보다 **“스키마랑 마이그레이션은 이렇게 고정하자”**가 먼저 나오고, 그 구현으로 Prisma·TypeORM·Django 같은 게 다시 필수에 가깝게 느껴지는 겁니다. SQL을 안다고 자부하던 사람들도, 코드가 사람 손이 아니라 공장처럼 찍힐 때는 “경계가 보이는 추상화”를 다시 옹호하게 되는 것도 재밌는 역설이에요.

물론 “챗이 SQL 잘 짜 주는데?” 하는 말도 맞습니다. 데모 한 판, PoC 한 번 돌리기엔 그만큼 편한 게 없어요. 다만 레포가 커지고 사람이 바뀌고, 누가 언제 스키마를 바꿨는지까지 책임져야 할 때는 “그날 챗이 준 SQL”만으로는 버티기 힘듭니다. 위상이 올라간 쪽은 ORM이라는 브랜드라기보다 남는 스키마에 가깝습니다.

바이브 코딩 실제 적용 사례

아래는 “이렇게 하라”가 아니라, 바이브 코딩(에이전트로 빠르게 짜기)을 ORM·DB 붙은 레포에 얹을 때 실제로 나오는 형태를 짧게 적은 겁니다. 회사마다 도구 이름은 다르고, 결이 비슷한 경우가 많아요.

사례 1 — 스키마를 대화에 고정
Cursor에서 @schema.prisma만 붙이고 “주문 API 추가해줘”라고 하면, 테이블 이름이 틀릴 확률이 확 줄어듭니다. 반대로 스키마 없이 말만 하면 user_profiles처럼 없는 테이블을 Prisma 쿼리에 박아 넣는 출력이 나오기 쉽고요. 로컬이 SQLite만 있으면 그 코드가 돌아가는 것처럼 보여서 스테이징 가서야 터지는 경우도 봤습니다.

레포 규칙 파일에 아래처럼 적어 두는 팀도 있습니다. 에이전트가 컨텍스트를 못 받아도 최소한의 방향은 잡힙니다.

에이전트가 따라야 할 “지도”는 결국 이런 조각입니다.

사례 2 — 마이그레이션만 먼저 머지
기능 한 방에 ALTER와 서비스 코드가 같이 들어오면, 리뷰에서 SQL은 훑고 넘기기 쉽습니다. 그래서 prisma/migrations/만 담은 PR을 먼저 올리고, 통과된 스키마를 기준으로 다음 PR에서 에이전트에게 “이 브랜치 기준으로 리졸버/서비스 짜줘”라고 시키는 식으로 쪼갠 팀이 있습니다. 락 오래 걸릴 변경은 그 첫 PR에서만 운영이랑 합을 맞추게 되고요.

로컬에서 스키마만 반영할 때는 보통 이런 흐름입니다(팀마다 스크립트 이름은 다름).

사례 3 — CI의 prisma generate가 막아 준 적
에이전트가 prisma.user.findByMagic() 같은 메서드를 지어내면, 로컬에서는 린트만 믿다가 넘어갈 수 있습니다. CI에 prisma generatetsc(또는 빌드)를 묶어 두면 머지 전에 빨간불이 납니다. “일단 any”로 때운 PR이 올라오는 것도 그 단계에서 걸러집니다.

package.json만 쓰는 팀은 "build": "prisma generate && tsc"처럼 빌드 앞에 generate를 붙이기도 합니다.

사례 4 — 같은 기능인데 프롬프트만 바꾼 경우
적용 전: “주문이랑 고객 같이 보여 주는 목록 API 만들어줘.”
적용 후: “schema.prisma의 Order·Customer만 쓰고, 목록은 N+1 안 나게 관계 로딩 한 번에.”
둘째도 틀릴 수는 있지만, 리뷰어가 스키마 파일과 생성 SQL을 같이 볼 명분이 생깁니다.

에이전트 출력이 대략 이렇게 갈리는지 리뷰에서 보게 됩니다.

사례 5 — 레거시 TypeORM을 당분간 유지할 때
Prisma로 갈아타기 전에, 에이전트에게 기존 entity 파일만 열어 주고 “새 테이블 추가 금지, 이 엔티티에 허용된 패턴만”이라고 잘라 준 팀이 있습니다. 문법이 섞인 한 파일이 머지되는 걸 막는 쪽에 가깝습니다.

에이전트가 확장해야 할 “윤곽”이 파일에 이미 박혀 있으면 덜 난장판이 됩니다.

정리하면, 바이브 코딩을 “말로만 빠르게” 쓰면 ORM이 오히려 환각 SQL을 더 잘 숨깁니다. 위 같은 식으로 스키마·PR·CI에 걸어 두면, 적용 사례가 쌓일수록 에이전트 출력도 그 레일 안에서만 움직이게 됩니다.

실무 적용 시 주의사항

사례까지 붙였다가 운영에서 터지는 건, 대개 아래 중 하나입니다. 에이전트가 코드를 늘리는 팀일수록 미리 팀 규칙으로 박아 두는 편이 낫습니다.

N+1·관계 로딩
목록 + 연관을 한 번에 안 가져오면 쿼리가 조용히 늘어납니다. 팀마다 include / preload / with 이름이 다르니, “목록 API는 기본으로 이렇게” 한 줄이라도 문서나 리뷰 체크리스트에 넣어 두세요. 에이전트 프롬프트에도 같은 말을 복붙하게 만드는 경우가 많습니다.

마이그레이션
migrate를 로컬에서만 돌리고 배포 파이프라인에 안 태우면, 프로덕션에서만 깨지는 패턴이 반복됩니다. CI에서 테스트 DB에 적용해 보고, 락 긴 ALTER는 배포 시간·플레이북을 따로 정합니다. 에이전트가 생성한 마이그레이션은 반드시 사람이 diff를 봅니다.

커넥션 풀
ORM이 풀을 숨겨도 인스턴스 수 × 풀 크기는 곱합니다. 서버리스·오토스케일이면 PgBouncer 같은 이야기로 이어지기 쉽습니다. “로컬에선 됐는데 스테이징에서만 커넥션 고갈”이 나오면 여기부터 의심하면 됩니다.

raw SQL
윈도우 함수·CTE·부분 인덱스는 ORM만으로 막히는 경우가 많습니다. 어느 디렉터리·어떤 레이어까지 raw 허용할지 합의하고, 문자열 붙이기 대신 바인딩은 필수로 둡니다. Prisma는 예를 들어 $queryRaw 계열이 공식 탈출구입니다.

테스트 DB
CI가 SQLite만 돌면 Postgres의 제약·락·타입 차이를 놓칩니다. 최소 한 단계는 운영과 같은 엔진으로 돌리는 편이 안전합니다.

읽기 복제
ORM 설정으로 읽기/쓰기를 나누면, 읽기 레플리카 글에서 말한 stale read(방금 쓴 데이터가 복제본에 아직 없음)가 앱 레이어까지 그대로 올라옵니다. “읽기는 항상 최신”을 약속할 수 없으면 라우팅이나 캐시 무효화 전략을 같이 정해야 합니다.

도구·조직 정합
DBA가 강한 조직에서 “앱이 스키마를 주도”하는 세팅은 충돌이 나기 쉽습니다. 반대로 작은 팀에선 스키마 파일이 코드에 있을수록 에이전트와 맞물리기 좋습니다. 제품 이름이 정답이 아니라, 누가 스키마를 승인하고 누가 마이그레이션을 머지하는지가 먼저입니다.

ORM을 잘 쓴다는 건 SQL을 안 쓴다는 뜻이 아니라, 반복은 도구에 맡기고 병목·무결성은 스키마와 SQL로 끝낸다는 쪽에 가깝습니다. 에이전트가 붙으면 그 차이가 더 빨리 드러납니다.

맺으며

ORM은 한때 멋있어 보이는 백엔드 서사에서는 밑바닥에 깔리기도 했습니다. 바이브 코딩이 들어오면서 코드가 너무 빨리 늘어나니, 반대로 스키마가 남고·리뷰 가능하고·빌드가 잡아주는 층이 다시 귀해졌어요. 위상이 하늘로 솟았다기보다, 천대받던 자리에서 다시 꼭 필요해 보이는 자리로 올라온 그림에 가깝습니다.

새 프로젝트면 README에 세 줄만이라도 적어 두세요. N+1 어떻게 막을지, 마이그레이션 누가 볼지, raw는 어디까지. 사람이 쓰든 에이전트가 쓰든 그게 없으면 또 예전처럼 욕먹는 층으로 돌아갑니다.

공유하기

관련 포스트