ORM은 여전히 안티 패턴인가요?
(github.com/getlago)- ORM은 “스타트업용 장난감”이라는 비판을 받지만, 실제 쟁점은 ORM 자체보다 잘못된 사용 방식에 가까움
- 객체 그래프와 관계형 테이블은 서로 다른 모델이라 패러다임 불일치가 생기지만, 이것만으로 ORM을 안티 패턴이라고 보기는 어려움
- SRP와 SOC 위반 논쟁은 유효하나, ORM은 애초에 두 데이터 모델을 연결하는 추상화라 원칙 위반 여부만으로 판단하기 어려움
- 성능 문제는 ORM보다 호스트 언어 로직으로 데이터를 조합하는 패턴에서 자주 생기며, ORM의 SQL 유사 기능으로 줄일 수 있음
- Lago가 Active Record 쿼리를 원시 SQL로 바꾼 핵심 이유는 속도보다 가시성과 디버깅이었고, 큰 쿼리에서는 원시 SQL 전환이 실무적으로 유리할 수 있음
ORM 비판을 보는 기본 입장
- ORM은 자주 “안티 패턴”으로 공격받지만, ORM 자체가 나쁘다는 결론은 과장에 가까움
- 다른 추상화와 마찬가지로 ORM도 완벽하지 않으며, 일부 가시성 손실과 성능 비용을 만들 수 있음
- Lago는 Ruby on Rails의 ORM인 Active Record에 의존하던 중 문제를 겪었고, 이를 계기로 ORM 의존 방식을 다시 검토함
- 결론은 ORM을 버려야 한다는 쪽이 아니라, ORM을 장단점이 뚜렷한 추상화로 다뤄야 한다는 쪽에 가까움
객체 모델과 관계형 데이터베이스의 차이
- ORM이 다루는 객체는 다른 노드를 가리키는 방향 그래프에 가까움
- 관계형 데이터베이스 테이블은 공유 키를 통해 데이터가 양방향으로 연결되는 무방향 그래프에 가까움
- ORM이 무방향 그래프를 흉내 낼 수는 있지만, 설정과 동작이 항상 단순하지는 않음
User객체에Posts배열이 빠질 수 있음Posts배열의 엔티티가 같은User객체로 되돌아가는 참조를 갖지 않을 수 있음- 되돌아가는 참조가 있어도 같은 객체가 아니라 복제된 객체일 수 있음
- 예를 들어 ORM은 사용자의 게시글을 배열로 반환하면서, 각 게시글에 작성자 역할의 사용자 역참조를 포함하지 않을 수 있음
- 이 불일치는 ORM에 대한 좋은 학술적 비판이지만, 실제 운영에서 더 큰 문제는 구체적인 사용 방식에서 드러남
SRP와 SOC 논쟁
-
단일 책임 원칙(SRP)
- ORM은 단일 책임 원칙(SRP) 을 어긴다는 비판을 받음
- 한 클래스나 계층이 여러 역할을 함께 맡게 만들 수 있기 때문임
- 데이터베이스와 트랜잭션 수행
- 레코드 표현
- 관계 정의
- 마이그레이션 생성과 실행
- 하지만 ORM의 목적 자체가 서로 다른 두 데이터 패러다임을 연결하는 것이므로, 일부 원칙 위반만으로 ORM을 부정하기는 어려움
-
관심사 분리(SOC)
- ORM은 관심사 분리(SOC) 원칙도 흐릴 수 있음
- SOC는 인프라 구성요소가 하나의 관심사만 가져야 한다는 원칙임
- ORM은 데이터베이스 관리를 백엔드 쪽으로 옮기기 때문에 SOC 위반으로 볼 여지가 있음
- 다만 오늘날 인프라 구성요소와 코딩 패턴은 성능, 지연시간, 코드 정리를 위해 여러 역할을 결합하기도 함
- OLAP 데이터베이스 안의 CPU 집계기
- 엣지 백엔드-프론트엔드
- 모노레포
성능 문제는 ORM보다 사용 방식에서 자주 생김
- ORM이 항상 비효율적이라는 비판은 대체로 틀림
- 많은 ORM은 개발자가 생각하는 것보다 훨씬 효율적으로 동작할 수 있음
- 문제는 ORM이 JavaScript나 Ruby 같은 호스트 언어 로직으로 데이터를 조합하기 쉽게 만든다는 점임
- TypeORM 예시에서는 특정 회사의 저자를 먼저 조회한 뒤, 각 저자별 게시글을 다시 조회하고, 각 게시글을 따로 저장하는 방식이 나쁜 패턴으로 다뤄짐
- 더 나은 방식은 TypeORM의
createQueryBuilder()로 단일 업데이트 쿼리를 구성하는 것임 - Lago의 청구 SQL 리팩터링에서도 Active Record와 원시 SQL 버전 사이에 성능 차이는 없었음
- Active Record의 데이터 결합 기능을 많이 사용했기 때문에 기존 쿼리도 이미 최적화된 상태였음
- 원시 SQL 재작성은 성능 개선보다 가시성 확보에 가까웠음
ORM이 실제로 느려질 수 있는 경우
- ORM이 원시 SQL만큼 항상 효율적인 것은 아니며, 일부 상황에서는 상당히 비효율적일 수 있음
- 첫 번째 문제는 ORM이 쿼리 결과를 객체로 변환할 때 큰 계산 오버헤드를 만들 수 있다는 점임
- TypeORM이 이 문제의 예로 언급됨
- 두 번째 문제는 일대다 또는 다대다 관계를 순회하면서 데이터베이스에 여러 번 왕복 요청을 보내는 경우임
- 이 패턴은 N+1 문제로 불림
- 원래 쿼리 1개에 N개의 하위 쿼리가 추가됨
- Prisma 예시에서는 사용자, 게시글, 댓글을 중첩 조회하면서 각 댓글에 대해 새 데이터베이스 요청이 발생할 수 있음
- N+1은 ORM에서 자주 나타나지만, 데이터 로더(data loader) 를 쓰면 N+1개의 쿼리를 2개의 쿼리로 접을 수 있음
- ORM의 일반적인 문제 상당수는 ORM 기능을 충분히 활용하면 피할 수 있음
더 큰 문제는 가시성과 디버깅
- ORM의 가장 큰 약점은 가시성임
- ORM은 사실상 쿼리 작성기이기 때문에, 명백한 원시 타입 오류 같은 경우를 제외하면 최종 오류 전달자가 아님
- SQL 오류가 반환되면 ORM이 이를 해석해 사용자에게 다시 전달해야 함
- Active Record는 이 지점에서 어려움이 있었고, Lago가 청구 구독 쿼리를 리팩터링한 이유도 여기에 있음
- 예상과 다른 결과가 나오면 다음 과정을 반복해야 했음
- 렌더링된 SQL 쿼리를 확인함
- 해당 SQL을 다시 실행함
- SQL 오류를 Active Record 변경으로 다시 옮김
- 이런 왕복 과정은 SQL 데이터베이스와 직접 상호작용하지 않기 위해 Active Record를 쓰던 원래 목적을 약화시킴
실무적 판단
- ORM은 올바르게 사용하면 원시 SQL에 가까운 효율을 낼 수 있음
- 많은 문제는 ORM의 SQL 유사 기능을 쓰지 않고 호스트 언어의 로직 구조에 너무 의존할 때 발생함
- 큰 쿼리가 개발자를 어렵게 만들고 디버깅을 방해한다면 원시 SQL 쿼리로 옮기는 것이 좋은 투자가 될 수 있음
- 대부분의 ORM은 ORM 내부에서 SQL 쿼리를 실행하는 기능을 제공함
댓글과 토론
Hacker News 의견들
-
Java에서 Hibernate가 막 나왔을 때부터 ORM을 써왔지만, 늘 별로였음
“다른 데이터베이스로 바꿀 수 있다”는 장점은 지금 보면 헛소리에 가깝고, 실제로는 아무도 그렇게 쓰지 않음
“SQL을 몰라도 된다”는 말도 틀렸고, 오래가는 비 trivial 앱은 결국 개별 쿼리를 문자열 수준에서 손봐야 함
데이터 계층은 쿼리 하나씩 문자열 SQL로 만들고 보간하는 방식이 맞으며, raw JDBC에 가까울수록 낫다고 봄
ORM이 “도메인 모델”을 지원한다는 주장도 나쁜 이유인데, 그 도메인 모델은 언제나 로직이 비어 있는 빈약한 도메인 모델이 되기 때문임
ORM, XML, 애너테이션, 생성된 SQL 디버깅 등에 낭비된 시간을 생각하면 울고 싶어짐- 정말 동의함. ORM은 “쉬운 부분은 조금 더 쉽게 만들지만, 어려운 부분은 정말 어렵게 만든다”고 표현하고 싶음
쉬운 부분은 이미 쉬우니 별로 관심 없고, 오히려 쿼리가 규모를 만나 무너지는 최악의 순간에 ORM 자체와 싸우느라 일이 백만 배 어려워진 적이 많았음
쿼리 빌더가 ORM과 완전히 같지는 않지만, https://gajus.medium.com/stop-using-knex-js-and-earn-30-bf41... 글을 좋아하고 Slonik류 기술의 팬이 됨
결과를 객체에 자동 바인딩하고 강한 타입 검사를 해주는 등 원하는 기능의 90%를 제공하면서도, 그냥 평범한 SQL을 쓰게 해줌
SQL이 신비롭게 어려운 언어라는 생각도 이해가 안 됨. 낡아서 흠은 있지만, ORM 도구가 만든 자체 쿼리 언어를 배우느니 SQL을 배우는 편이 낫다 - 오래전에 처음 써봤을 때부터 같은 이유로 ORM을 싫어했고 안티패턴이라고 봤음
그때는 이런 비판을 말하면 다른 개발자들이 내려다보곤 했음
지금도 개발자로서 억지로 써야 하는 도구와 기법 중 안티패턴이 많고, 업계의 불행한 현실은 널리 쓰이는 도구 상당수가 별로이며 안티패턴을 부추긴다는 것임
기술 업계에는 결정권을 쥔 소수의 연결 잘 된 개발자와 전직 개발자들이 있고, 그들이 별로라고 느낌 - 시작을 하필 현존 최악급 ORM으로 한 셈임
장단점 비율이 괜찮은 ORM도 있고, 특히 Python 쪽이 좋음
Django ORM은 투박하고 성능도 별로지만, 통합이 잘 되어 있고 실용적이며 생산적이고 사용감도 좋음
Peewee는 작은 패키지로 ORM을 제공해서, 대단한 기능이 필요 없고 귀찮을 때 작은 프로그램을 즐겁게 작성하게 해줌
SQLAlchemy는 더 많은 투자가 필요하지만 매우 유연하고, 깔끔한 SQL을 생성하며 동작도 정확함
객체지향 패러다임을 원하지 않고 관용적인 SQL 동작을 Python으로 추상화해 표현하고 싶을 때를 위한 더 낮은 수준의 쿼리 빌더도 노출함
결국 투자 대비 수익을 따지는 일반적인 엔지니어링 결정이 됨 - “문자열 수준에서 조정해야 한다”는 말은 내 경험상 헛소리임
10년 넘게 해왔지만, Hibernate 문서를 5분 읽고 그 상황에서 하라는 대로 하면 더 잘 처리할 수 없는 쿼리는 없었음
모든 개발자가 삼값 논리표와 COBOL식 함수명을 외워야 한다고 믿는 SQL 팬보이들이 정작 Hibernate 문서를 읽는 수고는 안 하려는 것처럼 보임 - “SQL을 몰라도 된다”는 건 조금이라도 생각이 있는 사람이 ORM의 장점으로 내세운 적이 없음
ORM은 OLTP 워크로드용이지 OLAP용이 아니며, Hibernate 창시자 중 한 명도 그렇게 말한 적이 있음
주된 효용은 길고 오류 나기 쉬운 INSERT/UPDATE 쿼리를 직접 쓰지 않게 해주고, SQL 행을 객체로 자동 매핑해주는 것뿐임
raw 방식으로 가면 그중 많은 부분을 다시 구현하게 되므로, 이것도 제로섬 게임이 아님
- 정말 동의함. ORM은 “쉬운 부분은 조금 더 쉽게 만들지만, 어려운 부분은 정말 어렵게 만든다”고 표현하고 싶음
-
충분히 복잡한 앱에서는 앱의 여러 부분이 함께 쿼리를 지연 조합할 수 있는 쿼리 빌더 같은 것이 언젠가는 생김
데이터를 메모리에서 합치고 필터링하고 정렬하는 대신 쿼리로 조합하게 되는 구조임
ORM은 이를 쉽게 만들 수 있는 도구이지만, 자신도 모르게 N+1 쿼리를 만들기 쉽게 해서 발등을 찍게 만들 수도 있음
모든 도구처럼 실제 사용 사례와 함께 트레이드오프를 따져야 하며, SQL 문장 문자열을 직접 조작하는 방식은 어떤 경우에도 추천하지 않음- ORM 경험은 사용하는 언어, 통합 수준, 작업 종류에 따라 극단적으로 다를 수 있음
그런데도 그 차이를 무시하고 ORM이 좋다거나 나쁘다고 절대적으로 말하는 사람이 항상 있음
그런 건 관심을 끌기 위한 논쟁적 태도로 보고 넘어가는 편이 낫고, “ORM은 이점이 있지만 어디서 유효한지 알고, 사용상의 문제가 이점을 넘지 않게 해야 한다”에서 출발하는 논의에 집중하고 싶음 - 충분히 복잡한 앱이면 쿼리 빌더가 필요해진다는 걸 당연하게 보지 않으며, 내 앱에서는 받아들이지 않음
데이터가 필요하면 데이터 접근 계층으로 가면 되고, 필요한 걸 제공하지 않으면 새 저장소나 제공자 등 패턴에 맞는 것을 만들면 됨
ORM이든 직접 만든 쿼리 조합기든, 앱이 “충분히 복잡해서 이게 필요하다”고 착각할 정도가 되면 이미 안정적으로 쓰기에는 너무 복잡해진 상태임
결국 잘못된 계층에서 빌더를 평가하거나, 결과를 순회하다가 N+1 쿼리를 만들고 있다는 걸 놓치게 됨
ORM은 필요 없음 - 이것이 2007년에 나온 LINQ가 하는 일에 가깝다고 봄
LINQ는 정확히 ORM은 아니고, 강하게 타입화된 LINQ 문장을 만들면 표현식 트리로 바뀌고, 다시 쿼리 제공자가 쿼리로 변환함 - ORM과 raw SQL을 둘 다 쓰는 입장에서 양쪽 모두 공감됨
개발 중에는 모델 안에서 ORM의 가독성을 선호하지만, 필요할 때 N+1과 다른 비효율을 면밀히 감시하고 최적화함
“필요할 때”의 경계는 명확하지 않지만, 너무 일찍 최적화하지 않으려 함
새 프로젝트는 데이터베이스에서 시작하는 편이고, 코딩 전에 팩토리, 시더, raw SQL 쿼리로 데이터 모델 감을 잡음
Laravel의 Eloquent 같은 ORM은 N+1을 해결하고 지연 로딩을 수행하는 좋은 메서드가 있지만, 언제나 트레이드오프가 있음 - ORM 개념을 뒤집는 라이브러리들도 있음
지연 쿼리 조합을 허용하는 라이브러리 대신, 쿼리를 직접 쓰면 라이브러리가 컴파일 시점에 그 쿼리용 코드를 생성함
예를 들어getUser: SELECT * FROM users WHERE id = ?같은 쿼리를 쓰면GetUserQuery클래스와getUser(id: String): GetUserQueryResult같은 메서드를 만들어주는 식이고, 이 모델이 훨씬 낫다고 봄
- ORM 경험은 사용하는 언어, 통합 수준, 작업 종류에 따라 극단적으로 다를 수 있음
-
ORM이 싫은 주된 이유는 현대 SQL 엔진을 과장된 멍청한 비트 저장소처럼 다루는 경향 때문임
CTE, LATERAL 조인, RETURNING 절이 처리를 크게 단순화하거나 데이터 불일치 가능성을 없앨 수 있는데도, ORM은 대체로 기본 테이블과 뷰를 미리 정의된 객체에 단순 매핑하는 수준에 머묾
ORM이 테이블까지 만들면 더 나빠짐
SQL은 본질적으로 데이터 추출 도구이면서 변환 언어인데, ORM은 이 면을 거의 무시해서 많은 개발자가 SQL에 INSERT/SELECT/UPDATE/DELETE 말고도 무엇이 있는지 모르게 됨
피벗 테이블, 시간 질의, CUBE/ROLLUP, 윈도 함수, 집합 반환 함수, 구체화 뷰, 외부 테이블, JSON 처리, 날짜 처리, 배제 제약, 범위·구간·도메인 같은 타입, 행 수준 보안, MERGE 등이 있음
완전한 공구 창고를 갖고도 누군가 망치, 드라이버, 쇠톱 하나씩만 건네주며 충분하다고 설득하는 꼴이고, 바로 몇 미터 옆에 테이블쏘, 라우터, 샌더, 쐐기 세트가 있는 줄도 모르고 커리어를 보내게 됨- 대부분의 ORM 프레임워크는 raw SQL 실행 기능이 있으므로, ORM을 쓴다고 이런 기능을 못 쓰는 건 아님
ORM은 대부분 단순 CRUD 문장 위주로 생각하게 만들지만, 그게 아마 더 낫고 고급 기능은 아껴서 써야 한다고 봄 - 데이터베이스가 아예 없는 프로젝트를 주로 해서 운이 좋다고 느낌
그래도 DB를 쓰게 되면 일부러 DB 기능의 아주 작은 부분집합만 쓰려 함
“코드”는 통제할 수 있는 영역이고, “데이터베이스”는 오류가 나중에 느린 통합 테스트 같은 곳에서 드러나는 위험하고 손이 닿기 어려운 영역으로 보기 때문임
DB 안에서 일어나는 로직은 저수준 테스트 범위 밖에 있어서 무섭고, 아마select-from-select쿼리조차 쓰지 않을 것 같음
공구 창고의 대부분은 기꺼이 남겨두겠음 - 그래서 많은 개발자가 SQL과 NoSQL의 차이를 못 보는 것도 놀랍지 않음
- 규모가 커지면 성능 목적으로 데이터베이스를 강하게 반정규화해서 과장된 멍청한 비트 저장소처럼 쓰는 일이 흔함
- “그런 화려한 데이터베이스 기능은 필요 없다”던 프로젝트를 몇 번 해봤고, 그 결과를 처리하는 건 답답했음
기본적인 데이터 무결성 보호 장치를 다시 발명하느라 대부분의 시간을 정교한 우회책을 만드는 데 쓰게 됨
자기만의 데이터베이스를 만드는 건 학습용으로는 재미있을 수 있지만, 프로덕션 앱에서는 아님
- 대부분의 ORM 프레임워크는 raw SQL 실행 기능이 있으므로, ORM을 쓴다고 이런 기능을 못 쓰는 건 아님
-
ActiveRecord는 항상 좋게 느껴졌음
일부 예외 상황은 있지만 80/20 지점을 잘 맞추고, 필요하면 SQL로 빠져나갈 수 있으며, 도메인 로직을 정리해주는 생명주기 훅도 훌륭하고 이해하기도 쉬움
모든 것을 “번들” 뒤에 숨기거나 자체 쿼리 언어를 도입하지 않는 실용적인 ORM은 좋음
ORM 문제를 완전히 “해결”하려는 ORM들이 다루기 어려움- ActiveRecord는 정말 쓰는 즐거움이 있고, 엣지 케이스를 만나면 언제든 raw SQL로 돌아갈 수 있음
ORM은 이렇게 만들어져야 함 - 큰 CRUD 앱을 ActiveRecord 없이 다시 만드는 건 상상하기 어렵고, 가독성과 유지보수성이 나빠질 것임
단, 올바르게 쓰려면 SQL에 대한 좋은 사전 지식이 필요함
사람들이 SQL을 배우지 않고 AR이 생성하는 쿼리와 한계를 이해하지 못할 때 문제가 생김 - ORM에 대한 불평이 잘 이해되지 않는데, 내가 써본 ORM이 ActiveRecord뿐이라서 그럴 수도 있음
- ActiveRecord는 정말 쓰는 즐거움이 있고, 엣지 케이스를 만나면 언제든 raw SQL로 돌아갈 수 있음
-
전제가 틀렸음. ORM이 한 번도 “해로운 아이디어”였던 건 아님
함께 일하기 끔찍한 ORM도 있지만, 그렇다고 장르 전체가 나쁜 생각이라는 뜻은 아님
SQLAlchemy는 SQL이라는 개념 자체를 재발명하려 하지 않아서 수년간 즐겁게 써왔음
대신 쿼리를 만들고 넘겨주는 편리한 계층을 제공하고, 자잘한 세부사항을 제대로 처리해줘서 직접 만지작거리지 않게 해줌- 내 ORM 경험은 거의 SQLAlchemy뿐이고 Ecto를 조금 배워본 정도라서, 이 스레드를 보면 다른 ORM들은 다 끔찍한 건지 묻게 됨
최근 Rust를 배우며 단순 CRUD를 처리하는 상용구 코드를 직접 쓰는 일이 고통스럽고 시간 낭비처럼 느껴짐
SQLAlchemy ORM은 더 복잡하거나 최적화하려는 쿼리에서는 조금 방해될 수 있지만, 그런 경우에는 언제든 직접 SQL로 내려갈 수 있음 - “begging the question”이라는 표현을 제대로 쓰는 걸 정말 오랜만에 봄
- SQLAlchemy는 코드가 쿼리와 맞아떨어지는 방식으로 자신 있게 SQL을 쓰게 해준다는 점에서 추천할 수 있음
- 내 ORM 경험은 거의 SQLAlchemy뿐이고 Ecto를 조금 배워본 정도라서, 이 스레드를 보면 다른 ORM들은 다 끔찍한 건지 묻게 됨
-
무엇이든 그렇듯 잘 맞는 상황과 그렇지 않은 상황이 있고, 기술은 쉽게 남용되거나 오용될 수 있음
예를 들어 .NET의 Entity Framework 같은 ORM은 특히 데이터베이스 우선 방식으로 작업할 때 큰 자산이 될 수 있음
수백 개 테이블과 올바른 외래 키 관계가 갖춰진 DB라면, ORM은 복잡한 스키마를 탐색하고 변경 시 컴파일 시점 검사를 제공하는 데 도움을 줌
내장 SQL은 성능상 이점처럼 보이는 양날의 검이지만, 특히 조인에서 하드코딩된 장황한 SQL을 유지보수하기 매우 어려움
테이블이 5~10개일 때가 아니라 수백 개일 때를 말하는 것임
누군가를 설득할 생각은 없지만, 모범 사례에 대해 인터넷에서 읽은 것을 그대로 믿지는 말아야 함
사람들이 서로 완전히 다른 대상을 두고 말하고 있을 수 있고, 어떤 기술을 쓰든 형편없는 개발자는 있기 마련임 -
ORM이나 다른 DB 인터페이스를 쓰지 않으면, 결국 CRUD 같은 것에 대해 직접 하나를 작성하고 유지보수하게 됨
그리고 그 안에는 이상한 버그, 엣지 케이스, 보안 구멍이 들어갈 가능성이 큼
SOLID 같은 원칙을 대학 수업에서 소프트웨어 설계 원칙으로 가르치는 데도 문제가 있음
학계에서는 시간, 유지보수성, 돈, 노력 같은 제약이 덜 중요하니 말이 되지만, 신입 졸업생이 현업에 들어오면 아주 나쁜 충돌이 생김
“완벽한” 해법을 찾느라 하루 종일 야크 털을 깎고 바퀴를 재발명하는 데 익숙해져 있기 때문임
이를 잡아줄 충분하고 유능한 시니어/리드 개발자가 없으면 아주 흥미로운 제품, 예산 초과, 대형 화재, 또는 그 조합이 나옴
더 나쁜 건 학계에서 만들고 학계를 위해 설계된 것이 대중에게 풀릴 때임
대표적인 좋은/나쁜 예가 OpenStack인데, CERN과 수백 대학의 특수 환경을 지원할 만큼 손잡이와 조절점이 많고 구성요소가 미친 듯이 모듈화되어 있지만, 너무 비잔틴식이라 시작하기가 거의 불가능함- 이 스레드에는 ORM이 공급자 약속과 맞지 않는다고 불평하는 내용이 많고, 그건 사실임
ORM은 데이터베이스를 노력 없이 바꿔주지 않고, 생성하는 SQL을 배우고 이해할 필요를 없애주지 않으며, 스키마를 매끄럽게 만들어주지도 않음
그래도 쓰는 이유는 어차피 복제해야 할 표준 추상화이기 때문임
DB 접근 캐싱을 가능하게 해주는데, 수동 SQL로는 규모가 커질수록 놀라울 만큼 어렵다
표준화된 도구와 모범 사례가 따라와서 N+1 같은 문제를 무관하게 만들 수 있음
ORM의 주된 문제는 잘못된 기대와 나쁜 ORM임
ORM의 가치를 알고 싶다면 ORM 없이 작성된 프로젝트를 열고 아무거나 바꿔보면 됨 - “결국 직접 하나를 만들게 된다”는 건 로직을 “행”이 아니라 객체 기준으로 구성하려고 고집할 때만 맞음
https://news.ycombinator.com/item?id=36498583 참고 - 몇몇 부서와 여러 회사가 초천재 한 명 또는 몇 명이 완벽을 좇는 바람에 고생하거나 실패했음
유용한 일이 진행되지 않거나, 빠르고 지저분한 접근보다 20~100배 오래 걸렸음
최악은 숙련된 HTML 디자이너 팀이 함수형 프로그래밍을 배우도록 강요받은 경우였음
백엔드 설정 마법사는 현대 공학과 우아한 설계의 경이였지만, 10명이 1년 걸려 만들었음
VC 돈은 사람을 이상하게 만듦 - SQLAlchemy에 대한 부정적인 얘기를 너무 많이 들어서 직접 가벼운 쿼리 빌더를 만들기로 했지만, 8개월 뒤 결국 SQLAlchemy를 쓰고 있음
SQL 언어는 매우 복잡하고, 제대로 된 모든 기능을 갖춘 쿼리 빌더를 만드는 건 정말 사소하지 않음
실제 관계형 매핑 부분은 논외로 해도 그렇다 - 좋은 ORM을 쓰는 게 낫고, 아니면 형편없는 ORM의 절반을 직접 떠안게 됨
- 이 스레드에는 ORM이 공급자 약속과 맞지 않는다고 불평하는 내용이 많고, 그건 사실임
-
SQL은 “그냥 그 빌어먹을 걸 직접 프로그래밍하라”가 맞는 사례라고 봄
SQL이나 ORM 라이브러리를 배우는 데 별 의미가 없고, SQL을 직접 배우고 쓰는 편이 낫다
데이터베이스 접근은 아마 커리어 내내 하게 될 근본적인 일이므로, 실제 기술을 직접 배우는 것이 최선임
CSS도 비슷해서 CSS 라이브러리를 배우기보다 CSS를 배우고 써야 함
사실 프런트엔드 개발의 거의 모든 면도 그렇고, “폼 라이브러리” 대신 브라우저의 폼 API를 직접 다루면 됨
개인적으로는 SQL이 어떤 ORM보다 훨씬 배우기 쉬웠음
잘못된 것을 배우는 비용을 낭비하지 말아야 함- SQL을 먼저 배우면 ORM을 써야 할 때도 알게 되고, ORM을 더 잘 평가할 수도 있음
CSS도 마찬가지로, CSS를 알면 편의를 위해 라이브러리를 고를 때 훨씬 더 유연하고 잘 이해하게 됨 - SQL은 영원한 언어라고 불려왔음
메인프레임, 데스크톱, 클라이언트/서버, 웹 등 여러 개발 플랫폼과 그 위의 다양한 언어를 거치면서도 SQL은 늘 공통된 실이었음
데이터 계층과 그 아래에서 무슨 일이 벌어지는지 잘 아는 개발자는 더 강한 개발자라는 데 완전히 동의함
ORM의 특이한 점을 배우는 데 시간을 쓸 거라면, 그 시간을 SQL 학습에 투자하는 게 왜 아니냐는 고전적 질문이 있음
SQL 경험의 복리는 10~15년 뒤 특정 플랫폼이나 언어의 ORM보다 더 가치 있을 가능성이 큼
ORM을 배우고 쓸 이유가 있긴 하지만, SQL을 배우기 싫다는 이유만으로 ORM 쪽으로 기우는 건 안 됨 - 핵심은 “추상화를 배우기 전에 기초를 먼저 배우라”에 더 가까움
그래야 그 추상화에 대해 무엇을 대가로 지불하는지 알 수 있음. 물론 “무비용 추상화”라면 다르겠지만 - 1992년부터 Pro*Pascal, CS130으로 SQL을 직접 써왔음
그런데 지금은 Go 타입과의 마샬링/언마샬링 때문에 ORM으로 분류될azer의crud를 쓰고 있음
Go 표준 라이브러리sql은 너무 기본적이라 객체 마샬링/언마샬링을 사실상 직접 써야 하고, 정말 지루함
요즘 ORM을 쓴다면 딱 그 용도로만 쓸 것 같음
- SQL을 먼저 배우면 ORM을 써야 할 때도 알게 되고, ORM을 더 잘 평가할 수도 있음
-
Dapper나 Diesel 같은 “중간 지점”의 마이크로 ORM을 좋아함
쿼리는 SQL로 쓰되, 강한 타입과 결과 집합의 객체 자동 매핑을 얻을 수 있음
생산성과 완전한 기능을 갖춘 “진짜” 쿼리 언어를 함께 얻는 셈임
또 객체 매퍼는 읽기 전용 쿼리에만 쓰는 방식도 좋아하게 됨
업데이트는 명령 패턴, 즉 저장 프로시저로 수행하고, 이를 네이티브 메서드처럼 보이게 매핑할 수 있음- Dapper는 지금까지 써본 데이터 접근 해법 중 가장 즐거웠음
개발자들은 SQL이 없는 척하지 말고 그냥 받아들여야 함 - 모델 함수가 결국 SQL로만 매핑되도록 직접 쓰거나, 파라미터화된 쿼리를 올바르게 수행하기 위한 상용구 PHP를 쓰지 않아도 되는 점이 좋음
Eloquent는 그런 잡일을 충분히 추상화해줘서 로직에 집중할 수 있게 함
배열과 쿼리뿐인 “오래된” PHP 앱을 모델과 Eloquent를 쓰는 구조로 다시 작성 중인데, 완전히 판도가 바뀌는 느낌임 - 중간 지점이 좋음. TypeScript에는 예를 들어 Kysely나 내 프로젝트인 Zapatos가 있음
- 이게 최적 지점이라고 봄
예전에 .NET에서 SQLite를 쓸 때도 그런 방향의 래퍼 라이브러리를 만들었음: https://github.com/zmj/sqlite-fast
- Dapper는 지금까지 써본 데이터 접근 해법 중 가장 즐거웠음
-
이 스레드는 ORM의 마인드셰어 지배력을 잘 보여줌
거의 모든 댓글에서 ORM의 암묵적 대안이 앱 코드 안 문자열 리터럴로 된 raw SQL임
이미 6년 전부터 SQL을 하나의 언어로 쓰고, 쿼리를 앱에 메서드로 노출하는 래퍼를 생성하는 도구들이 있었음
queryfirst, pgtyped, pugsql, sqlc 같은 것들임
이것은 패러다임 전환이거나 적어도 그렇게 될 수 있었고, 명확히 더 우월한 작업 방식임이 드러나지만, ORM과 변하지 않는 이 논의가 산소를 모두 빨아들여 변두리에 머물러 있음- 맞음. Go에는 sqlc도 있음
SQL은 언어이고, 그것도 매우 고수준 언어임
더 저수준 언어로 SQL을 이어 붙이려는 건 “망치를 들면 모든 문제가 못으로 보인다”의 대표 사례임
예를 들어 저수준인 어셈블리로 코딩하면서 큰 시스템과 상호작용하는데, 그 시스템 문서가 입력은 JavaScript로만 표현할 수 있다고 한다면 선택지가 있음
순수 어셈블리로 뭔가를 이어 붙이게 해주는 난해한 라이브러리를 가져와xxx.js파일을 저장소에 커밋하지 않을 수도 있고, 그냥.js파일로 고수준 언어의 힘을 써서 표현할 수도 있음 - sqlc를 한 프로젝트에서 써본 뒤로는 다시 돌아갈 수 없을 것 같음
사용 중인 SQL 쿼리에 대한 단일 진실 공급원이 생기고, 실제로 작성하고 유지보수하는 대상이 쿼리 자체라서 새 인덱스를 만드는 식의 최적화도 쉬워짐 - ActiveRecord는 항상 별로였고, 그 내부에서 쿼리를 구성하는 arel은 항상 마음에 들었음
raw SQL 문자열 위에 추상화를 두는 건 매우 유용하지만, 관계를 객체인 척하는 건 복잡하게 꼬인 방식임 - NeXT의 Enterprise Objects Framework는 25년 넘게 전에 이미 이를 지원했음
TopLink도 아마 그랬을 것임
저장 프로시저와 로컬 엔티티 간 매핑을 처리하던 방식이 이랬음 - 어떤 도구들인지 궁금함
- 맞음. Go에는 sqlc도 있음