Photo by Annie Spratt on Unsplash
오늘도 여전히 사이드프로젝트를 진행하며 함께 하는 형님과의 대화에서 또다른 깨달음을 얻었습니다.
들어가기
최근 회사에서 시간이 남아, Querydsl 을 사내서비스에 적용하는 방안을 고려하기 위해서 학습하던 중, 나왔던 이야기입니다. Querydsl, SwaggerCodegen, ProtocalBuffer 등의 도구들은 이전에 가졌던 문제를 제 3의 관점으로 넘겨, 개발자는 비지니스 로직에만 집중 할 수 있게 해준다는 관점에서 좋은 도구라는 이야기를 남겼었습니다.
그러나, 이전에 지인과 식사를 하면서 가볍게 Querydsl과 Jpa 의 단점을 이야기 하면서 나눴던 부분에 대해서 내용을 남겨두면 좋겠다 싶어 이렇게 포스팅하게 되었습니다.
우리가 활용하는 JPA와 Querydsl, hibernate.
제가 알고 있는 JPA와 Querydsl 의 지식은 깊지 않을 수 있습니다. 그러므로, 결코, 저는 하이버네이트나 JPA, Querydsl 이 나쁘다라고 말하고자 말하고자 하는것이 아님을 명백한 밝히겠습니다.
왜 JPA, 하이버네이트가 발생하게 되었을까?
JPA, Hibernate 가 발생하게된 배경을 살펴보면, 우리들의 서비스는 대부분 객체를 관계형 DB에 관리합니다. 그러므로, SQL 중심으로 개발이 이루어지면서 동시에, 반복적인 SQL Query 작성이 끊임없이 발생하게 됩니다. 또한 SQL를 작성해야만 하는 의존적인 개발을 피할수가 없을 것입니다.
즉, 객체와 SQL의 불편한 동거를 해결하기 위해서 객체 중심의 SQL을 작성할 수는 없을까? 라는 질문을 시작으로 지금의 JPA와 하이버네이트가 만들어졌습니다.
둘 사이의 패러다임은 완벽하게 불일치하기 때문에, 이를 해결하기 위해 JPA는 다양한 장치와 기법을 활용하게 됩니다.
JPA를 어느 정도 알고 있다는 전제하여 이야기하면, JPA하면 @Entity, @Repository, @Id 등 많은 어노테이션이 존재하고, 도메인 영역에 해당되는 객체에 어노테이션을 붙여 데이터베이스를 관리합니다.
우리는 이런 도구를 흔히 Object-relational mapping(객체 관계 매핑) 즉, ORM이라고 부릅니다. 객체는 객체대로 설계하면, 관계형 데이터베이스는 관계형 데이터베이스대로, 중간의 ORM은 둘 사이의 매핑을 해주는 도구가 됩니다.
그래서 제가 말하고 싶은건,
다시한번 JPA가 나쁜 도구가 아니라는 것을 밝히며, 흔히 복잡성을 설명할 때 2가지로 나눕니다.
필연적 복잡성과 우연적 복잡성
본질적(필연적) 복잡성 : 소프트웨어 구현하고자 하는 기능에 대한 복잡성
우연적 복잡성 : 소프트웨어를 구현하는 행위(언어, 툴, 라이브러리 등)에 따른 복잡성
여기서 우리가 제거할 수 있는 복잡성은 우연적 복잡성이 됩니다.
위 2가지의 복잡성을 개발자로 하여금, 어떤 도구를 활용할 때 좋은 지표가 될 수 있습니다. 내가 활용하고 있는 이 도구는 필연적으로 복잡한 문제인건지, 우연적 복잡성인지 판단할 수 있습니다.
앞서 말한 2가지의 복잡성으로 하이버네이트와 JPA를 바라봅시다. 어떻게 생각하시나요? 사이드 프로젝트를 함께하는 지인과 이야기를 나누며 내렸던 결론, JPA, 하이버네이트는 우연적 복잡성에 속하며, 이는 장기적으로 바라볼 때 우리의 서비스를 더욱 복잡하게 만드는 수단으로 동작될 확률을 높이게 된다. 라는 결론에 다다랐습니다.
실제로, JPA를 강의해주는 김영한 강사님도 JPA를 제대로 알고 쓰는 개발자는 극히 드물다는 표현을 쓰셨습니다. 이 말은 즉슨, 정확하게 JPA의 장점을 활용하기란 어렵다 라는 이야기입니다.
분명 하이버네이트와 JPA는 객체와 RDBS의 간의 불일치한 부분을 해결해준 고마운 도구입니다. 이런 도구 덕분에, 우리는 SQL에 대한 생각을 조금 덜 하게 되고, 비지니스 로직에 집중하게 되니까요. 얼마나 좋아요. 어노테이션 하나로 모든게 끝나버리는 관계 맵핑. 기타 등등 개발의 편리성을 가져다 줍니다. 그러나, 단점도 분명히 존재합니다.
1. 애노테이션의 범람, 그리고 의존성
이전에 Spring5를 왜 사용해야 하는가? 라는 포스트에서도 Spring4 가 너무 많은 어노테이션을 가져 익혀야될 것도 많고, 스프링 프레임워크에 심하게 의존되는 현상이 발생한다는 이야기를 했었습니다. 또한 어노테이션은 어떤 기능을 동작시키기 위해 만들어진 것이 아닙니다. 어노테이션은 말 그대로 어노테이션일뿐 타입이 아니기 때문에 본 기능의 목적과 맞지 않습니다. 또한 컴파일러에의해 검증 가능한 타입이 아니며, 코드의 행위를 규정하지 않습니다. 더군다나 상속이나 확장 규칙의 표준이 아닙니다.
2. Layerd Architecture를 일부 위배합니다.
그리고 JPA를 활용할 때, 우리는 각 도메인 객체에 @Entity 와 같은 애노테이션을 붙이게 됩니다. 이는
우리가 흔히 알고 있는 위 Layered Architecture 에서 일부 반하는 행위를 합니다. JPA는 인프라스트럭처에 해당하는데, 인프라스트럭쳐가 도메인을 알게되는 기현상이 발생하게 되는거죠. 분명히 JPA는 이 문제를 안고 있습니다. 이런 문제 때문에 jooq 를 사용하는 사람도 많고, jooq 를 활용하는 대다수는 그런 문제를 알고 jooq를 활용합니다. 참고링크
3. 빈약한 Bean 을 생성하는 주범이다.
JPA를 활용하기 위해서 우리는 모든 엔티티를 객체화해야 하므로, 빈약한 도메인 모델을 설계할 가능성이 높아집니다.
4. 우연한 복잡성을 야기시킨다.
저는 Java 를 활용한 백엔드 개발자입니다. 그리고 필연적으로 데이터베이스를 이해해야만 하고, 활용할 줄 아는 것도 하나의 역량이라고 생각합니다. 우리는 흔히 이런 이야기를 합니다 "JPA만 사용하니까, SQL을 작성하는 방법을 까먹었어." 그럼 이건 JPA의 문제인가요? SQL의 문제인가요?
JPA를 기술 스택에 도입하는 순간부터 우리는 JPA와 SQL 모두를 알아야만 하는 우연한 복잡성을 만들어 냅니다. 그리고 이는 개발자로 하여금 피료감을 누적시킨다고 생각합니다.
이제 Querydsl의 이야기가 나오는군요. Querydsl은 왜 나온걸까요? JPA의 Criterial API 의 복잡성을 대체하고, JPQL을 조금 더 편하게 만들기 위한 빌더로서 출연하게 됩니다. 그럼 개발자 입장에서는 또 다시 Querydsl을 학습해야만 합니다. 이미 SQL 하나를 편하게 하기 위해서 우리는 jpa, querydsl 을 학습해야 하는 우연한 복잡성을 만들어내고 있는 것입니다.
그럼 Jpa와 하이버네이트, querydsl 은 사용하면 안되는건가요?
아닙니다. 앞서 말씀드린 것처럼 개발의 편의성을 높일 수 있는 획기적인 아이디어를 채택해 만들어낸 산물입니다. 또한 영속성 컨테스트를 잘 활용할 수 있다면, 더할 나위없는 성능을 만들어 낼 수 있을 것입니다.
그러나, 말씀드린 것처럼, 잘 활용해야 합니다. N+1 문제, LazyInitalizationException 문제 등 이런 문제를 해결하기 위해서는 잘 활용해야 합니다. 즉, 결론은 적재적소로 활용할 줄 알아야 한다는 것이 결론입니다.
마지막으로 슬랙에서 지인과 나눴던 대화를 마지막으로 포스팅을 마치고자 합니다.
감사합니다.
햇님: 대부분 함수형쪽에서 기존 객체지향 을 가지고 엔터프라이즈 를 개발하면서 발생하는 복잡성을 비판하면서 이야기를 많이해
햇님: 우리가 본 DDD책에서도 나와
햇님: http://redutan.github.io/2015/10/10/dddq-chapter-01
DevOOOOOOOOPDevOOOOOOOOP
DDDQ 1장 도메인 주도 설계란 무엇인가?
MJ's DevOOOOOOOOP blog
햇님: 엔지니어링이라하면 디자인인데 디자인은 잡스에 말에 따르면 어떻게 동작하냐인데 ..
그걸 잘 생각해보면 어떻게 동작하냐의 의미는 비지니스가 어떻게 동작하냐 이지 프레임워크나 라이브러리나 통신이 어떻게 동작하냐를 말하는게 아님
나: 앗 저희가 공부한 책에서도 나오는군요. 집에가서 한번더 살펴봐야겠습니다.
햇님: 비지니스가 가진 본연의 필요적 복잡성을 그대로들어내지 않고 우연적 복잡성으러 그것을 가려버리면 그건 병신 같은짓이야
햇님: 그래서 내가 하이버네이트 JPA를 병신처럼 쓰는걸 싫어해
햇님: 너가 공부하는 쿼리dsl을 왜쓰는걸까? ORM을 생각해보면 멍청한짓이야
나: 햇님, 그래서 우연적 복잡성을 띄어버리는 하이버네이트를 과거에 사람들은 이렇게 복잡해질거란 사실을 몰랐겠죠 ㅋㅋ 그러다가, 점차 커지고 커지고 하다가 보니, 복잡해지고 이 복잡한 기술을 해결하기 위해서 또 다른 querydsl 과 같은 도구가 등장하고
햇님: 그래서 jooq가 욕하고 있는거지
나: ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
햇님: 하이버네이트는 JPA 는 빈약한 빈을 만들어내가 한 가장 큰주범이야
햇님: 이것도 DDD 에 나와 ㅋ
햇님: 또 생각해보면, 자바처럼 역사가 있는 언어가 점차 진화되는 것처럼 JPA도 진화하는건 아닐까요?
햇님: 정규야 JPA는 ORM 에 대한 SPI 야
햇님: 그 구현벤더가 하이버네이트이고
햇님: ORM은 오브젝트와 릴레이션 간 불합치성을 해결하기위해서 나온 맴핑 프레임웍이야
나: 맞습니다 ㅋㅋ
햇님: 불합치한것을 억지로 껴맞추려 하는 게 오래 갈까 모르겠다.
나: 이 불일치를 해결하려다가 보니 더 불편해지는.. 불상사가
햇님: 너도 보면알겠지만 이제 RDB 가 사용되는 곳이 점점 사라져 폴리그랏 DB 시대야
햇님: 결국 불합치한 걸 매꾸기 위해서 query dsl , 네이티브 쿼리가 나왔는데 이거 sql 아냐?
나: 결과적으로는 sql이죠
나: 다시 처음으로 회구ㅣ~~~ 하는 것처럼 보입니다. (edited)
햇님: 결국 jooq가 이야기한대로 왜 sql 그 자체가 오랜 시간 성숙해 dsl 로 자리 잡았는데 그걸 억지로 뒤로 숨기는 것
햇님: sql 그 자체를 객체로 올려버리면 되는것을
햇님: 그동안 개발자들이 orm 을 사용하려 했던것은 rddb frist 개발을 고수 했기때문이야
햇님: 왜냐 ?
햇님: 문자열로 표현되는 sql은 쉽게 부서지고 취약하거든
햇님: 그리고 유지보수가 안되니
햇님: 객체를 한커풀 덛데서 보면 먼가 그럴싸하거든
나: ㅋㅋ 너무 공감되네요
나: findById(1L) 이거네요.
햇님: 함수형언어측에서 orm 을 사용하느걸 봤어?
나: 아녀 ㅋㅋㅋㅋ
햇님: 당연하지 그들은 포커스하는것이 data 에대한 해석이야
나: 방금 소름돋았어요
햇님: 그렇기 때문에 fluent 한 환경에서의 데이터 해석이 중요해
햇님: 과연 우리가 객체를 가지고 멀할려고 하는지 생각해봐
햇님: 넣다 뺏다 하려는걸 할려고 orm 을 우연적 복잡성을 가지고 사용할만한 가치가 있나?
햇님: 내가 14년동안 orm 을 깊게 파지 않아도 대부분은 필요없어서 sql을 사용해도 큰무리 없었어
나: 그건 그래요. 실제로 JPA하다가 복잡한 조회의 경우에는 JPQL? SQL비스무리한 형태의 코드를 만들죠
나: JPA가 가진 장점도 명확하게 집어주긴 해야 될 것같아요. 햇님, 말씀대로 분명히, JPA는 패러다임의 불일치를 해결하려는 경향으로 우연한 복잡성을 키웠습니다. 그러나, 흔히 JPA에서 말하는 영속성 컨테스트, 1차캐시 이것은 큰 도움이 되는거 아닐까요?
영속성 컨테스트의 성능을 어디까지 맛보았는지, 저는 테스트 해보지는 않았습니다만, 잘 활용된다면, 성능적인 효율성 있는게 아닐까 그런 생각이 듭니다.
햇님: 성능적인 효율이 있는곳은 결국 조인이 들어간곳들인데 성능적 효율이 필요한 정도라면 어그리케이션을 확인해봐야해
햇님: 내가 하고 싶은 말은 그런 적당한 크기의 어그리케이션에서 사용되는것은 좋은데 (객체그래프가 존재하는) 그런곳은 많이 드물어 요즘 환경에서는
햇님: 구체화된 뷰를 만들어내는 cqrs 에서나 검색을 es를 통해서 지원하는게 rdb를 지원하는격이니
햇님: 비대한 객체그래프를 사용하기 위해서 orm을 쓴다면 먼저 비대한 객체그래프가 정말 단일 어그리케이션 인지 확인이 필요해
햇님: 여튼 우리가 이야기하고 잇는 핵심은
햇님: 우연적 복잡성을 개발자가 얼마나 잘 컨트롤 하게 하는 …지원하는 프레임워크이고 페러다임인가야
햇님: 복잡성 의존성의 시대잖아 MSA 환경으로 가면서
햇님: 모놀리틱 환경에서는 ORM 이 맞을 수 있어 한덩어리 로 이루어져있으니
햇님: 진흙탕 안에서 갈라치기 해봤자지
나: 크으… 설득 당해버렸습니다. 햇님.
햇님: 내가 하고 싶은말은 ORM 이 절대 병신 같다가 아니라 ORM을 아무런 생각없이 사용하는게 병신같다는거지 특히 쿼리dsl 쓰는거보면
나: ‘사실 문제는 설계 또는 외부에 있는데, 이 부분을 우연적 복잡성(like querydsl, hibernate)에 의존해 해결하려고 한다.’ 라고 생각해도 되는걸까요??
햇님: ㅇㅇ 그렇게 생각해도 되고 적재적소에 올바르게 쓰자 … 사실 다들 생각안하고 그냥 쓰잖아
햇님: 세상에 IOC 컨테이너가 spring 만 있는줄 알고 … 이젠 spring 이 머져 ? 전 부트는 아는데 하는것처럼
나: (뜨-끔) ㅎㅎ (edited)
햇님: 다시 읽어보니 오타 왜캐많으냐? (edited)
햇님: ㅋㅋㅋ 햇님 찰떡같이 알아들었습니다. 첫번째 링크 내용이 신선하네요. 단순한건 쉬운것이 아니다. 이런 내용이 있는 사이트요.
햇님: ‘사실 문제는 설계 또는 외부에 있는데, 이 부분을 우연적 복잡성(like querydsl, hibernate)에 의존해 해결하려고 한다.’ 라고 생각해도 되는걸까요??
=>> 비지니스 디자인 / 설계 / 구현을 위해 사용하는 툴이 발생하는 우연적 복잡성을 간과 하고 이를 해소하려는 고민이 없다는게 아쉬운 부분이지
'가치관 쌓기 > 개발 돌아보기' 카테고리의 다른 글
빠르게 실패하기(fail-fast) VS 안전하게 실패하기(fail-safe) (4) | 2021.04.16 |
---|---|
동시성에 대해서 생각해보자. (0) | 2021.02.09 |
JPA 연관 관계를 명시(사용)하는 이유는 무엇일까? (0) | 2021.01.16 |
테스트 주도 설계를 실천한다는 것 (0) | 2020.12.18 |
코딩은 어떻게 해야하는가? - 1 (0) | 2020.07.25 |
댓글