본문 바로가기

JPA

[MyBatis/JPA] (이론) MyBatis -> JPA 전환 과정

💡목표

MyBatis로 작성된 코드를 JPA로 변환해보자.

 

 

 

📕 과정1. 목적을 정확히 생각하자

기본적인 목적은, 퍼시스턴스 레이어(MyBatis,JPA)의 변환다른 레이어 코드 변경을 일으키지 않게 하면서(핵심),

MyBatis를 JPA로 변환하는 것이다.

 

단,

  • ⭐ 다른 레이어의 코드 변경 없이 수행되어야 한다.
  • 기존 시스템의 기능은 모두 유지되면서 수행되어야 한다
  • MyBatis 자체를 JPA로 바꾸면 안되고, 두 기술스택이 병행되어야 한다(그 후에 MyBatis 제거)
  • 사용자가 사용하던 API는 그대로 사용해야 한다.
  • Response 타입이 바뀌면 안 된다.

만약 서비스 코드에서 MyBatis 레포지토리를 JPA 레포지토리로 그냥 바꿔버린다면, JPA 레포지토리의 코드가 바뀜에 따라 서비스가 동작하지 않을 수도 있다.

즉, '고수준 모듈인 서비스'가 '저수준 모듈인 JPA 레포지토리'에 영향을 받기 때문에 이는 DIP에 위반된다.

 

 

 

📗 과정2. 구조 설계해보기

먼저 결정할 수 있는 요소들부터 생각해보자.

 

1. DIP 준수를 위해 상위 인터페이스는 필수다.

2. 컨트롤러/서비스/레포지토리 계층 중,  MyBatis과 JPA를 나눌 계층을 하나 선택해야 한다.

 

어쨌던 DIP를 지키기 위해서는 상위 인터페이스를 하나 두고, 그 아래에 MyBatis과 JPA를 두어야 한다. 그리고 컨트롤러, 서비스, 레포지토리 계층 중 어떤 것을 기준으로 MyBatis와 JPA를 나눌 것인지 결정해야 한다.

 

 

1️⃣ 먼저 컨트롤러 계층에서 MyBatis과 JPA를 나눈다고 생각해보자.

[ API 중복 문제 ]

- 컨트롤러 레벨에서 MyBatis와 JPA를 분기하면 동일한 기능을 하는 API를 두 번 작성해야 한다.

예를 들어 '/user/{id}'와 같은 엔드포인트를 MyBatis용과 JPA용으로 각각 두 개 만들어야 하는데, 이는 코드 중복을 야기할 뿐만 아니라 사용자가 이미 사용하고 있는 API를 그대로 사용해야 한다는 조건을 만족시키지 못한다.

 

[ 코드 복잡성 증가 ]

- 두 가지 기술 스택을 각각의 컨트롤러, 서비스, 레포지토리로 나누어 관리하게 되면 전체 코드가 복잡해진다. 이는 전체 시스템을 이해하고 관리하는 것을 어렵게 한다.

 

 

2️⃣ 서비스 계층에서 MyBatis과 JPA를 나눈다고 생각해보자.

위 사진과 같이 MyBatis와 JPA 서비스를 구현하는 상위 서비스 인터페이스를 만들 수 있다. 그러나 이것도 좋은 방법은 아니다.

 

[ 코드 복잡성 증가 ]

- 위의 이유와 마찬가지로, MyBatis 서비스 말고도 JPA 서비스 코드를 또 만들어야 한다. JPA 레포지토리도 개별적으로 만들어야 한다. 이는 전체 코드를 복잡하게 만든다. 

 

 

=> 애초에 MyBatis와 JPA는 데이터 액세스 계층(레포지토리 계층)이기 때문에 컨트롤러나 서비스로 나누는 것은 비효율적이다.

 

 

 

3️⃣ 레포지토리 계층에서 MyBatis과 JPA를 나눈다고 생각해보자.

MyBatis와 JPA 레포지토리를 상속하는 상위 레포지토리 인터페이스를 만들 수 있다.

 

[ 코드 중복 방지 ]

MyBatis와 JPA를 각각의 컨트롤러나 서비스로 분리하지 않기 때문에 동일한 기능을 중복해서 구현할 필요가 없다.

이는 코드 중복을 방지하고 전체 코드를 간결하게 유지하는 데 도움이 된다.

 

[ 일관된 API 유지 ]

하나의 컨트롤러와 서비스에서 MyBatis와 JPA 레포지토리를 모두 사용할 수 있기 때문에, 외부에 제공하는 API가 일관성을 유지할 수 있다. 이는 사용자가 변경 없이 기존 API를 계속 사용할 수 있게 한다.

 

[ DIP 준수 ]

서비스 레이어(고수준 모듈)는 상위 인터페이스에 의존하기 때문에 MyBatis나 JPA 레포지토리(저수준 모듈)에 의존하지 않는다. 이를 통해 DIP를 준수하고, 시스템의 유연성을 높일 수 있다.

 

[ 구현체 교체 용이 ]

특정 구현체(MyBatis 또는 JPA)를 다른 구현체로 교체할 때, 서비스나 컨트롤러 레이어의 변경 없이 레포지토리 구현체만 교체하면 된다.

 

 

=> 그렇다면 상위 레포지토리를 생성하여, MyBatis와 JPA 레포지토리를 상속하는 것까지 확정이다.

 

 

 

 

📘 과정3. 적용시켜 보며 문제점 찾기

제일 먼저 했던 행동은 아래 사진과 같이 그냥 상위 인터페이스에 두 개의 인터페이스를 직접 상속시켜본 것이다.

 

그러나 문제점이 발생했는데, MyBatis와 JPA 레포지토리의 메서드 이름이 다르다면, 상위 레포지토리에서 동시에 두 가지의 메서드 이름을 쓸 수는 없기에, 하나로 통일해야 했다.

 

MyBatis 레포지토리의 메서드 이름은 변경될 수 없다. 서비스 계층에 영향을 주기 때문이다. 그렇다면 JPA 레포지토리의 메서드 이름을 MyBatis의 메서드 이름으로 통일시켜야 할까? 

JPA Repository는 CrudRepository의 상속을 받기 때문에, 기본적으로 제공받는 메서드가 존재한다. 이것을 쓰지 않고 MyBatis의 메서드 이름으로 통일시킨다는 것은 조금 비효율적으로 보인다.

 

 

그렇다면 어떻게 해야 할까?

문제 상황을 좀 더 살펴보자.

 

 

상위 인터페이스를 OrderRepository,

MyBatis와 JPA 레포지토리를 각각 OrderMyBatisRepository, OrderJPARepository라고 하겠다.

 

MyBatis 레포지토리의 메서드 이름은 변경될 수 없으므로, OrderRepository의 메서드 이름을 OrderMyBatisRepository의 메서드 이름으로 통일시킨다고 가정했을 때, 한 가지 문제 상황을 느낄 수 있다.

 

 

바로 OrderRepository(위 사진에서는 Target)의 메서드와 OrderJPARepository(Adaptee) 메서드가 '호환되지 않는다'는 것이다.

따라서 어댑터 패턴을 이용하여 이 문제 상황을 해결해볼 수 있을거라는 생각이 들었다.

 

 

어댑터 패턴은 두 클래스가 '호환되지 않을 때' 사용하는 디자인 패턴으로,

Adapter 클래스를 이용하여, 호환되지 않는 Adaptee 클래스를 Target 인터페이스로 변환시켜 호환이 되도록 한다.

 

즉, OrderJPARepository'Adapter' 클래스를 이용하여, 호환되지 않는 OrderJPARepository를 OrderRepository 인터페이스로 변환시켜 OrderJPARepository와 OrderRepository 인터페이스가 호환되도록 하면 된다.

 

 

자세한 코드 내용은 다음 포스팅에.