프로젝트 일기

[LIME 리팩토링] - 연관관계 제거

Chemi___6_oj 2024. 1. 27. 22:44

서론

연관관계는 편하다. 정말 편하기 때문에 이유없이 사용하기 마련이다. 공부를 하기 전까지는 이렇게밖에 생각할 수 없었다.

내가 원하는 정보를 쉽게 탐색해오고 사용할 수 있었기 때문에 그어떤 문제도 발생하지 않아 편리하게 사용했다. 

그런데, 언젠가 객체 관점에서 생각하는 경우 이렇게 마음대로 탐색할 수 있다는 장점 뒤에 불필요한 단점들도 존재할 것이라고 생각했고,

JPA를 조금 비판적인 시각으로 바라보고자 했다. 그때 연관관계를 제거하는 생각을 먼저 하게되었는데, 놀랍게도 구글링해보면 JPA 영속성 관계 연관관계 책 내용을 개인이 재정리하는 글들이 많이 보였다.

그래서 이번 기회에 이런 주제를 가지고 객체지향을 바라보는 방법에 대해 생각해야겠다 싶어 지금과 같은 글을 작성하게 되어써다. 

 

연관관계를 제거하는 이유1 - 강결합

JPA를 제외하고 생각해보자.  연관관계는 객체 참조다.

A가 B에 대해 알고 있다고 하자.(의존 하고 있다.) A의 정보를 가져오면, B에 대한 정보를 가져와야 한다.

 

그러면 이런 질문들이 따라온다.

 

그러면 그 B의 정보가 많거나 하는 경우라면..? => 뚱뚱한 객체가 될거다.

그리고 그 B에 대한 정보가 필요하지 않은 상황이라면? => 불필요한 객체를 달고 산다.

B의 정보가 수정된다면..? => 영향을 고스란히 감수한다.

 

이만큼 A가 B의 정보를 들고 있는건 상당한 부담이 될 수 있다. JPA의 그래프 탐색만을 생각한다면, 이런 특징들을 놓쳐갈 수 있다. 

 

연관관계를 제거하는 이유2 - 생명주기

생명주기가 다르다면 그 정보를 가지고 있을 필요가 없다. 그저 불필요한 혹주머니가 된다.

개발자가 개발하기 위해 컴퓨터가 없으면 안되지만, 개발하기 위해 숟가락은 없어도 된다.

 

물론 연관관계가 없어도 정보탐색은 할 수 있다. id값만 들고 있는 방식으로 fk역할은 하지만 fk는 아닌 것처럼 그런 형태로 간접적인 연결로 해결할 수 있다. 있다가 보여줄 예시도 이와 같다.

 

자동차가 움직이기 위해서는 바퀴가 필요하고, 자동차가 없어지면 바퀴도 없어진다. => 이러면 연관관계를 걸어줄 이유는 될 수 있다.

이것도 요구사항에 따라서 걸어줄지 말지도 개발자가 결정하게 된다.

 

연관관계를 제거하는 이유3 - 그 외의 이유

우선 앞선 두가지 조건을 충족한다면, 연관관계를 붙여줄지 말지 고민하게 될거다.

예시를 한번 들어보자. bucket - bucketItem - item이 있다. bucket과 item은 다대다 관계로 이루어져있고 bucketItem은 매핑이다. bucket은 여러개의 item정보를 가지고 있어야 한다. 그러면, 이들은 다대다 양방향 관계로 만드는게 좋을까?

 

물론 만들어도 아무문제 없지만, 조금 보수적인 관점으로 바라보려고 한다.

bucket과 item만 이루어진 세상이 아니라, item과 member, item과 review와 같이 item은 여러 관계에 얽혀있을 수 있다.

item자체도 모든사람들이 item을 재료로 여러 기능을 할 수 있는 base가 되는 큰 의미를 가지고 있고, bucket도 의미상 내가 가지고 싶은 버킷리스트처럼 사용되고 있다. 둘다 큰 독립적인 쓰임을 가지고 있다면, 조금 보수적으로 생각해서 연관관계를 사용하지 않는게 더 이점이 크다고 생각했다. (기술로부터의 분리도 고려함) 

 

그러면 어떤 경우에 연관관계를 맺어줄까?

메뉴 -> 메뉴 옵션 -> 메뉴별 세부 옵션 처럼 서로 의미 결합되어있어도 문제가 없고 생명주기가 같으며, 서로 관계가 깊은 경우에는 자유롭게 정보를 드나들며 사용해도 된다.

 

이렇게 특별한 상황에 한해서만 연관관계를 사용하며 기술과 객체지향을 신경쓰는 코드를 작성해보려고 한다.

 

실습

 기존 매핑테이블

가장 많이 변경이 이루어지는 곳은 매핑테이블이다. 객체참조에서 id참조로 변경했다.

이렇게 변경을 하게 되면 bucket과 item간에 매핑 객체를 생성해줘야하는 번거로움이 생기지만, 결합도를 낮출 수 있게 되었다.

 

이후 생긴 trouble슈팅이지만, querydsl을 사용하는 과정에서 새롭게 배운 점이 있었다.

기존 코드에서는 문제가 없던 쿼리 코드를 수정해야 했다. bucketItem에서 item으로 객체 그래프 탐색을 진행했지만, 이제는 그렇게 해결하지 못하므로 join을 한번 더 걸어서 bucket - bucketItem - item간에 3중 join을 해줘서 해결했다. 

 

기존 Bucket -> 제거 후 bucket

bucket도메인의 경우 양방향편의 메소드를 제거하게 되고 불필요한 정보를 줄여 객체의 독립성을 어느정도 갖추게 되었다.

 

 

기존 bucketReader -> 제거 후 bucketReader

정보를 읽어오는과정도 로직이 조금 더 간결해진 모습을 볼 수 있다.

 itemReader로 정보를 가져오기 위해선 bucket -> bucketItem -> item 순서로 탐색해야만 했는데,

그 과정을 생략할 수 있게 되었다.

 

 

기존 bucketAppender -> 제거후 bucketAppender

먼저 bucket정보를 save하고 기존 itemId, bucketId로 매핑 객체를 생해 저장해준다.

약간 절차지향적인같다(?)는 생각이긴 하지만, 완전한 객체지향은 만들 수 없으니 이정도면 괜찮겠다 싶었다.

 

 

생각보다 많은 코드가 변경되었다. 하지만, 그 영향은 도메인 계층까지만 있었고, 그 앞 레이어 코드에는 영향이 가지 않았다.

그리고 여전히 db에서 정보를 조회하기 위해 쿼리가 많이 나가고 있는 문제도 확인했다. 여전히 이 부분은 개선해나가야 할 부분이라고 생각한다.(batch를 도입을 생각중..) 

 

테스트코드도 마찬가지로 수정하게 되었지만, reader와 같이 정보를 조회해오는 쪽에서는 의외로 모든 코드를 수정하지 않아도 괜찮아서 그래도 코드를 생각하면서 짰구나 라고 느꼈다.. 기술로부터의 분리를 하는 첫걸음이었는데, 나름 많은 공부가 된 것 같다.