해당 포스팅은 사이드 프로젝트 진행 중 겪은 크고 작은 이슈들에 대한 기록입니다.
읽기에 앞서
- 이전 포스팅을 먼저 보고 오시는 걸 추천드립니다!
대체하며 마주하게 된 문제
- @ElementCollection을 변경하고 별도의 엔티티로 이를 대체하면서 정말 많은 고생을 하였다.
1. 만들어진 기능들을 생각보다 많이 리팩토링해야 함
- 기존의 방식을 버리고 새로운 방식을 도입해서 그런지 기존의 매장 save, update 로직 및 프론트 단 모두 대대적인 리팩토링 작업이 필요했다.
- 사실 @ElementCollection의 문제점을 알고도 몸소 직접 겪어보고자 이를 사용했었기에 바꿔야한다는 것은 인지하고 있었으나, 막상 이를 시도하려니 연관 관계 매핑부터 기존의 비즈니스 로직까지 정말 많은 부분을 수정해야 했다.
2. Update 기능에서 조건 분기가 상당히 까다로움
- Save 기능은 새로 만든 엔티티들을 기준으로 발생할 수 있는 예외만 잘 처리하면 크게 어려운 것이 없으나 문제는 Update 쪽에 있다.
- Update 로직의 조건 분기는 아래와 같다.
List<DaysOfStore> prevDaysOfStoreList = findStore.getDaysOfStoreList();
if (daysIdList == null) {
if (prevDaysOfStoreList != null) {
daysRepository.deleteByStoreId(findStore.getId());
}
} else {
List<Days> daysList = daysIdList.stream().map(id -> daysRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("잘못된 요일값입니다."))).collect(Collectors.toList());
if (prevDaysOfStoreList.size() == daysList.size()) {
for (int i = 0; i < prevDaysOfStoreList.size(); i++) {
prevDaysOfStoreList.get(i).updateDays(daysList.get(i));
}
}
if (prevDaysOfStoreList.size() < daysList.size()) {
int i;
for (i = 0; i < prevDaysOfStoreList.size(); i++) {
prevDaysOfStoreList.get(i).updateDays(daysList.get(i));
}
for (int j = i; j < daysList.size(); j++) {
linkDaysAndStore(daysList.get(j), findStore);
}
}
if (prevDaysOfStoreList.size() > daysList.size()) {
int i;
for (i = 0; i < daysList.size(); i++) {
prevDaysOfStoreList.get(i).updateDays(daysList.get(i));
}
for (int j = i; j < prevDaysOfStoreList.size(); j++) {
daysRepository.deleteByDaysIdLinkedDaysOfStore(prevDaysOfStoreList.get(j).getDays().getId());
}
}
}
2-1. 기존의 휴무와 새로 입력된 휴무의 개수가 같거나 새로 입력된 휴무의 개수가 더 많다면?
- 매장의 휴무는 일~토까지 총 7개이다.(일요일이 1번, 토요일이 6번)
- 만약 매장의 휴무가 [토, 일]이었다가 [금, 토, 일]로 변경됐다고 가정해보자.
- 단순하게 생각하면 기존의 데이터에 [금]에 해당하는 값이 DaysOfStore에 들어간다고 느낀다. 하지만 이를 정말 단순하게 구현하면 [금 -> 일], [토 -> 금], [토] 이렇게 총 3번의 쿼리가 날라간다.
- 즉, Store에서 가지고 있던 DaysOfStoreList의 요소들의 순서가 새로 들어온 Days와 일치하지 않기 때문에 값이 다르면 매번 Update 쿼리가 날라가거나 Delete 쿼리가 날라갈 수 있다.
2-2. 만약 기존의 휴무보다 새로 입력된 휴무의 개수가 더 작다면?
- 이 경우엔 더 문제인게, 단순하게 구현하면 데이터 수정조차 똑바로 이루어지지 않는다.
- 기존 휴무가 [수, 목, 금, 토]인 경우 이를 [수, 목]으로 변경하는 경우엔 정상적으로 기능이 작동한다.
- 앞의 2개의 데이터는 같은 Days를 의미하므로 업데이트 쿼리가 나가지 않고 (정상), [금, 토] 2개의 데이터는 지워졌으므로 DaysOfStore 테이블에 Delete 쿼리가 2번 날라가면서 정상적으로 삭제된다. (정상)
- 문제는 기존 휴무의 데이터를 순서에 맞게 줄이는 것이 아니라 다른 식으로 줄이면 발생한다.
- 예를 들어 [수, 목, 금, 토]인 경우 이를 [금, 토]로 변경하는 경우 문제가 발생한다.
- 현재 내가 사용하는 로직을 보면 연결된 DaysOfStoreList의 요소 삭제 조건을 Days의 ID로 사용하고 있는데, 위의 상황에서 보다시피 같은 Days가 존재하는 경우에 기존값만 삭제하는 것이 아닌 새로 바뀐 값마저 삭제하고 있다. 이로 인해 최종적으로 결과가 NULL이 반환된다.
- 또한 만약 NULL이 되지 않게 뒤의 [금, 토]만 remove하는 경우, 쿼리가 Update 2번, Delete 2번 총 4번의 쿼리가 날라가므로 성능상으로도 이로울 것이 하나도 없다.
위의 문제들로 인하여 고민하게 된 점
- @ElementCollection을 사용하지 않고 대체하는 것이 정말 올바른 설계인가에 대한 고민에 빠졌다.
- 사실 앞서 언급한 다양한 문제점이 있기에 가급적으로 사용하지 않는 것이 좋은 건 맞겠지만, 만약 정말 단순히 값만 담을 별 의미가 없는 필드라면 많은 에너지를 쏟지 않고 @ElementCollection을 사용하는 것이 더 좋지 않나라는 생각이 든다.
- 내가 아직 올바른 방향을 잡지 못하였기에 코드도 난잡하고 어려워 그런지 유지보수에서도 좋지 못한 느낌이고 생각보다 Update의 조건 분기가 너무 까다롭기에 그냥 통째로 삭제하고 새로 값을 넣는 것이 오히려 더 좋은 방법이지 않을까?라는 느낌...
- 결과적으론 조건 분기만 잘 잡는다면 DB와의 통신 횟수를 기존보다 확실하게 줄일 수 있는 것도 맞고, 현재 프로젝트에서 휴무는 아무런 기능도 하지 않는 데이터가 아니기에 (휴무 기준 조회 등등) 데이터 추적을 위해서라도 별도의 엔티티를 사용하는 방향을 고집하였다.
결론
- 한계점이 있는 것을 알기에도 이 기능을 사용한 것은 내가 직접 이러한 문제점들을 겪어보고 개선하는 과정이 필요하다고 생각됐기 때문이다.
- 실제 나가는 쿼리들과 식별자의 부재로 인한 불편사항들을 몸소 겪으면서 서비스의 성능 개선에 대하여 더욱 진지하게 고민하게 되었고, 의지를 가져 이를 개선하는 것 또한 즐길 수 있게 되었다.