Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1 - 3단계 방탈출 사용자 예약] 몰리(김지민) 미션 제출합니다. #22

Merged
merged 92 commits into from
May 6, 2024

Conversation

jminkkk
Copy link

@jminkkk jminkkk commented May 2, 2024

안녕하세요 비밥!
이번 미션 리뷰 받게 된 몰리라고 합니다.
이번 단계 변경 사항은 다음과 같습니다. 링크

참고하실 부분

패키지 구조

현재 패키지 구조는 도메인형으로 작성되어 있습니다.
코드 리뷰하실 때 미리 참고하시면 더 수월하실 것 같아요 😁

reservation
	⎿ controller
	⎿ service
	⎿ dao
	⎿ dto

theme
	⎿ controller
	⎿ service
	⎿ dao
	⎿ dto
// ...    
🤔 도메인형 패키지 구조를 선호하는 이유

특정 기능에 대해 수정이 필요해서 관련 Domain, Service등 을 수정해야 경우,
관련 도메인이 있는 패키지를 먼저 보고 그것과 연관된 패키지들만 열어놓는 것이 패키지를 헷갈릴 일이 더 적었다고 느껴서 선호합니다..!

DTO 네이밍

패키지 구조와 관련해 도메인형 구조에서는, 행위가 먼저 나오는 것이 빠르게 인식할 수 있을 것이라고 생각하여 아래와 같은 컨벤션을 따라 작성하였습니다.

<행위> <자원> <Response/Request>
🤔 도메인형 패키지 구조에서 행위를 먼저 작성했던 이유

만약 동료 개발자가 예약 조회 API의 응답을 수정해야 한다면

/reservation 패키지 확인 -> /dto 확인 -> 조회에 대한 dto 확인 

순서로 확인을 할 것 같다고 생각했습니다. 그러다보니 dto 파일 이름에 행위가 먼저 나올 때 더 빠르게 찾을 수 있을 것 같아 행위를 먼저 작성하도록 했습니다!


추가 질문 사항은 코멘트를 통해 작성하겠습니다.
리뷰 잘 부탁드립니다 🙂

jminkkk and others added 30 commits April 30, 2024 14:47
- ReservationRepository에서 예약이 존재하는 지 확인하는 쿼리문을 날짜와 시간만 비교

Co-authored-by: seunghye218 <[email protected]>
jminkkk added 27 commits May 4, 2024 16:32
- ReservationTimeRepository에 existsByStartAt 추가
- ReservationTime에 isSameStartAt 추가
Copy link
Author

@jminkkk jminkkk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 비밥! 오랜만입니다 😁
첫 리뷰 이후 이것저것 반영해보느라 2차 리뷰요청이 늦어졌어요 😭

1차 리뷰 이후 변경 사항이 조금 많은 것 같아서, 1차 리뷰 반영에 대한 변경 사항을 첨부합니다!

1차 리뷰 반영에 대한 변경 사항

주요 변경 사항

검증, 예외처리 보완

첫 제출 당시 시간 여유가 많이 부족했는데요 🥲
특히 검증과 예외처리 부분이 많이 아쉬워서 해당 부분을 치밀하게 반영하고자 노력했습니다!

테스트 개선

이전에는 더미데이터를 테스트용 db에 넣어두고 테스트를 진행했었는데요.
가독성 부분에서 매우 안좋다는 문제점을 느꼈어요.
또 계층별 테스트에서도 부족했던 부분이 많은 것 같아서 이 부분도 개선하고자 했습니다.

때문에 이번 pr에서는 테스트에 대해 개선하고자 제일 노력한 것 같아요...ㅎㅎ

테스트를 수정하면서 거짓 양성에 대한 정도가 매우매우 높았었구나를 느꼈습니다...
다른 개발자가 유지보수를 해야되는 상황이였다면 야근과 함께 저를 원망했을 것 같아요 😭


다른 참고사항은 코멘트로 작성해놓겠습니다!
이번 리뷰도 잘 부탁드립니다 🙇

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class ViewControllerConfiguration implements WebMvcConfigurer {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 템플릿 파일만 렌더링하는 역할만 하는 AdminController, UserController 가 있었는데요.
두 컨트롤러 모두, 별다른 로직 없이 단순 렌더링만 처리했었습니다.

따라서 다음과 같은 하나의 클래스를 만들어서, 뷰 매핑에 대한 처리를 담당하게 하는 것이 변경 사항을 쉽게 관리할 수 있을 것이라는 생각이 들었습니다!

Comment on lines 103 to 106
// stub
Mockito.when(reservationTimeRepository.findById(1L)).thenReturn(Optional.of(super.getReservationTimeById(1L)));
Mockito.when(themeRepository.findById(1L)).thenReturn(Optional.of(super.getThemeById(1L)));
Mockito.when(reservationRepository.existsByDateAndTimeAndTheme(LocalDate.of(2024, 10, 10), 1L, 1L)).thenReturn(true);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 Mock를 사용했던 방식에서 Fake 객체를 활용해서 개선을 해보았는데요!
테스트 메서드 내에서 흐름도 더 잘보이고,
필요한 데이터만 제공하면 내부 동작까지는 생각할 필요없이 동작을 검증할 수 있다는 장점이 있는 것 같아요.
또 테스트 짜기에도 더 수월했던 것 같아요 🙌

좋은 피드백 감사합니다!🤗

Copy link

@pci2676 pci2676 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지난번 리뷰에 대해 반영해주신 것 확인했습니다. 👍

다음단계를 진행함에 있어 고민해보시면 좋을 부분에 리뷰를 남겨두었으니 확인 바랍니다.

머지하겠습니다. 🚀


@ExceptionHandler
public ResponseEntity<String> catchValidationException(MethodArgumentNotValidException ex) {
return ResponseEntity.badRequest().body(ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage() );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디버그 포인트를 잡아보시고 validate를 통과하지 못할 요청을 보내보시면 error가 여러개 들어오는것을 확인하실 수 있습니다.

그러면 지금 처럼 get(0)이 아닌 다른 모습으로 개선하실 수 있을것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검증 에러가 여러 개 올 수도 있는 것을 미쳐 몰랐네요 🥲
MethodArgumentNotValidException를 잘 알아보지 않고 사용해서 문제였던 것 같아요 개선하겠습니다!

Comment on lines +38 to +40
Reservation reservation = createReservationRequest.toReservation(reservationTime, theme);

validateReservationIsPast(reservation);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reservation을 생성하고 validate를 하지 않는 다면 올바른 상태를 가지지 않은 Reservation 객체를 사용할 수 있어보입니다.

다시말해 생성과 검증이 분리되어 있는것 같아보입니다.

이를 조금 더 방어적으로 대응할 수 있는 방법에 대해 고민해보시면 좋을것 같습니다.

Comment on lines +46 to +54
private ReservationTime findReservationTime(final Long id) {
return reservationTimeRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("생성하려는 예약의 예약 시간이 존재하지 않습니다."));
}

private Theme findTheme(final Long id) {
return themeRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("생성하려는 테마가 존재하지 않습니다."));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exception이 발생하게 된 세부적인 사항을 담아서 exception 메세지를 구성한다면 운영할때 큰 도움이 될 수 있을것 같습니다.

Comment on lines +82 to +90
@Override
public boolean existsById(final Long id) {
String sql = """
select count(*)
from theme as t
where t.id = ?
""";
return jdbcTemplate.queryForObject(sql, Integer.class, id) != 0;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿼리 성능에 대한 리뷰라 지금은 한번 읽고 넘어가셔도 좋습니다.

존재하는지에 대한 여부를 확인할때는 count 집계보다 limit 1 절을 이용하는 것이 더 효율적입니다.

count는 집계를 하기 위해 where절에 해당하는 모든 데이터를 검색해야하는 반면 limit은 1개의 집계결과가 탐색되면 거기서 탐색이 중지되기 때문입니다.

다시 말씀드리지만 지금은 쿼리에 집중해야하는 때는 아니니 쿼리에 대한 공부는 천천히 시간을 가지고 해보시면 좋을것 같습니다.

Comment on lines +68 to +80
@Override
public List<Theme> findOrderByReservation() {
String sql = """
select t.id, t.name, t.description, t.thumbnail, count(t.id) as count
from theme as t
left join reservation as r
on r.theme_id = t.id
group by t.id
order by count desc
limit 10
""";
return jdbcTemplate.query(sql, themeRowMapper);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정책의 변경사항이 생겼을때 유동적인 대응이 필요할 수 있습니다.

가령 상위 10개의 테마가 아닌 3개만 보여주어야 한다는 등의 변화가 있을 경우

서버측은 변경사항이 없이 대응할 수 있으려면 limit절에 들어가는 10이라는 상수는 매개변수로 전달받아도 좋을것 같습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

또한 한번쯤 고민해보시면 좋을것 같은 포인트를 리뷰를 드려보면

해당 테마로 예약된 예약의 건수가 10만건 100만건 1000만건이 넘어간다면 위 쿼리로는 대응이 불가능해질 수 있습니다.

이를 해결할 수 있는 방법이 무엇이 있을지 고민해보시면 좋을것 같습니다.

지금 단계에서 반영할 정도의 내용은 아니니 고민만 해보셔도 좋습니다.
다른 크루들과 이야기해보아도 좋을것 같습니다.

import roomescape.reservationtime.model.ReservationTime;
import roomescape.theme.model.Theme;

public class Reservation {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

무언가 개선을 바라고 드리는 리뷰는 아닙니다.

다만 도메인 모델과 데이터 영속성 모델을 1:1로 대응하여 바라보는 것을 경계해보시면 좋을것 같습니다.

비즈니스 영역에서 필요한 도메인 모델이 데이터 영속성 모델로 동일하게 대응되어 저장되어야 한다는 법은 없으니까요.

@pci2676 pci2676 merged commit bda7a3b into woowacourse:jminkkk May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants