용로그
article thumbnail

들어가며


개인적으로 요즘 개발 트렌드에서 Spring Data JPA가 공식이 되었다고 생각될 만큼, 개발자들이 ORM(Object Relational Mapping)이라는 것을 많이 사용합니다. 하지만 많이 사용하는 것과 잘 사용하는 것은 명백히 다르다고 생각합니다.

 

실제로 Spring Data JPA, JPA, Hibernate의 관계와 배경을 설명할 수 있는 사람이 몇이나 될까요?

 

최근에 Spring Data JPA를 사용하여 개발하는 도중 Spring Data JPA는 기본적인 구현체들을 극단적으로 추상화시키고 있다는 생각이 들었습니다. Spring Data JPA 공식 문서에서는 Spring Data JPA에 대해서 다음과 같은 정의를 내놓았습니다.

 

더 큰 Spring Data 제품군의 일부인 Spring Data JPA를 사용하면 JPA 기반 리포지토리를 쉽게 구현할 수 있다. 이 모듈에서는 JPA 기반 데이터 액세스 계층에 대한 향상된 지원을 다룬다. 데이터 액세스 기술을 사용하는 Spring 기반 애플리케이션을 더 쉽게 구축할 수 있다.

 

즉, Spring Data JPA를 사용한다면 기존의 DAO 계층의 반복적인 구현을 할 필요가 사라집니다. 이렇게 본다면 Spring Data JPA는 개발자들의 입장에서 GOAT으로 보입니다.

 

하지만, 이게 가능한 이유는 JPA 번들인 Spring Data JPA는 JPA(Java Persistence API)를 추상화시키고, JPA는 Hibernate를 추상화하고 있기 때문입니다.

 

이 글에서는 Spring Data JPA가 제공하는 JpaRepository에 대한 조심스러운 메시지를 Hibernate의 공식 문서를 참고하여 전해보고자 합니다.

 

Hibernate가 주장하는 Repository의 불필요성


Hibernate가 표준화되고 이후 Java 엔터프라이즈 개발에서 JPA가 승격되기 전인 Java EE 시절에는 현재 Hibernate가 처리하는 지저분한 JDBC 상호 작용을 수동적으로 구현하는 것이 일반적이었습니다.

 

2001년 Hibernate가 혜성처럼 등장했을 때 개발자들 사이에서 인기를 얻었습니다. 하지만 당시 Hibernate는 표준 사양이 없었기 때문에 일부 사람들이 Hibernate에 대한 의존성을 줄이거나 최소화하려고 했습니다.

 

당시에 의존성을 줄이기 위한 방법은 DAO를 사용하여 그 내부의 JDBC 구현체를 Hibernate에 대한 호출로 대체하는 것이었습니다. 실제로 Hibernate에서는 DAO의 사용을 권장할 만큼 적극적으로 지원하였지만, 이것은 실수였고 이를 인식하는데 너무 오랜 시간이 걸렸다고 합니다. 그 이유는 아래서 설명합니다.

 

JPA의 등장

JPA 1.0의 승인으로 상황이 변했을 때 ORM 매핑을 수행하는 표준 방법이 제공되었습니다. 많은 고품질의 JPA 구현체가 등장하고, JPA의 등장으로 DAO가 사라질 줄 알았다고 합니다.

 

JPA는 EntityManager라는 것을 지원하는데, Hibernate에서는 본질적으로 EntityManager가 표준 Repository이며, 명확한 사양이 정의된 인터페이스라고 주장합니다. 즉, EntityManager라는 표준 Repository가 제공되기에 DAO가 사라질 것이라고 생각했다는 것이죠.

 

하지만 지금 우리(나와 독자)들이 사용하는 방식처럼 기존의 DAO는 "Repository"라는 것으로 대체되고 JPA의 앞단에 존재하는 추상화된 계층으로 계속 사용되고 있습니다.

 

특히 JPA EntityManager는 본질적으로 표준 Repository이며 명확한 사양이 정의된 것으로, EntityManager를 제공하는 것 이상으로 유용한 것이 없다면 Repository 프레임워크는 불필요한 것으로 여겨진다고 진다며, Hibernate에서는 이는 Repository가 코드에 불필요한 복잡성과 부풀려짐을 추가한다고 주장합니다.

 

불필요한 영속성 계층은 복잡성을 유발한다.

궁극적으로 별도의 영속성 계층이 전혀 필요하지 않을 수도 있습니다. 예를들어 서비스 로직에서 EntityManager를 직접 호출하는 것을 상상해 보죠.

 

영속성 계층이 존재하는 것과 존재하지 않는 것

EntityManager를 바라보는 한 가지 방법은 시스템의 모든 엔터티에 대해 작동하는 하나의 Repository로 생각하는 것입니다. 이러한 관점에서 보면 JPA는 영속성 계층이죠. 하지만 Hibernate에서는 Repository를 사용하지 말아야 하는 이유를 제시하였습니다.

  • EntityManager를 단일 범용 Repository로 생각하면 JPA가 이미 영속성 계층을 제공하는 것으로 볼 수 있으며, 이를 Repository로 한 번 더 추상화를 시키는 "2단 추상화"를 사용할 필요가 없다.
  • 대부분의 쿼리는 여러 엔터티에 영향을 미치며, 특정 부분에서만 사용된다. 또한 Repository를 사용한다고 해서 서로 상호 작용하지 않거나 공통된 내부 구현 세부 정보를 공유하지 않는다.
  • Repository는 일반적으로 낮은 응집력을 갖는다. Repository 계층은 대부분의 경우에 단일 엔터티에 대한 구현만 가지고 있으며, 이로 인해 클라이언트와 강하게 결합된다.
  • Mock Repository를 사용하여 테스트하는 것은 가치를 가질 수 없으며, H2와 같은 인메모리 데이터베이스를 사용하여 테스트를 실행하는 것이 더 나은 대안이다.

예를 들어 User와 Order 두 가지 엔터티에 대한 Repository가 존재한다고 가정해 봅시다. UserRepository와 OrderRepository의 메서드는 일반적으로 직접적인 관련이 없으며, 공통 내부 구현을 공유할 필요가 없습니다. 따라서 이러한 Repository들은 서로 독립적으로 동작하며 별다른 상호 작용이 없다고 이해할 수 있습니다.

 

하지만 이러한 특징을 가지는 Repository를 사용하게 된다면 항상 엔터티별 Repository를 만들어야 하며, 코드베이스에 불필요한 복잡성이 추가될 수 있습니다.

 

결론적으로 Repository 패턴을 사용하는 대신 JPA의 EntityManager를 활용하고, 추가적인 추상화를 피하는 것이 더 나은 선택일 수도 있다는 것입니다.

 

마치며


사실 이 문제는 jakarta(옛 java) 진영에서 오랫동안 다룬 문제이기도 합니다. 특히 Spring Data JPA가 등장하면서 이런 이데올로기가 난립하는 문제가 심해지지 않았나 생각이 듭니다.

 

그런데 국내에서는 JpaRepository를 사용하는 이유와 같은 키워드로 검색할 시 단지 "편해서", "구현하지 않아도 돼서"라는 의도의 블로그가 수도 없이 검색됩니다. 실제로 JpaRepository를 사용하지 말자는 글을 본적이 단 한 번도 없습니다. 물론 글이 없다고 해서 단정 지을 수 없지만, 어느 정도의 비율은 판단할 수 있을 거라 생각됩니다.

 

이 이유에 대해 굉장히 조심스럽게 이야기를 꺼내보자면, 국내 개발 생태계에는 비교적 최근에 JPA가 자리 잡기 시작했습니다. 그렇기 때문에 Hibernate가 등장하고 JPA 그리고 지금의 Spring Data JPA가 만들어지기까지의 변천사를 아는 개발자들의 비율이 많지 않다고 생각합니다.

 

개발자들이 학습을 하지 않아서일까요? 그렇지는 않다고 생각합니다. 저는 JPA가 그 배경과 사상에 대해 생각할 틈이 주어지지 않을 정도로 빠르게 자리를 잡았다고 생각합니다. 경험해보지 못한 문제에 대해 단지 편하게만 해결하려고 하니 이런 고민도 생각해보지 못한 것이 아닐까 반성되네요.

 

또한 이 글에서는 Repository에 대해 비판적인 의견을 내놓았지만, Spring Data JPA 또는 Repository 자체가 나쁘고 사용하지 말자는 것은 절대 아닙니다. Spring Data JPA가 만들어짐으로써 DAO 계층의 간편한 구현이 가능해졌다는 것은 부정할 수 없으니까요.

 

다만, 우리가 너무 "추상화된 것"에 의존하고 있지는 않은지 다시 돌이켜 보자는 뜻이죠. 또한 그러한 장점이 실제 Hibernate의 의도를 흐리고 있지는 않은지 다시 생각해봐야 한다고 생각합니다.

profile

용로그

@용로그

벨덩보단 용덩 github.com/wonyongChoi05