-
Notifications
You must be signed in to change notification settings - Fork 82
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
[Team - 06][BE] 에어비앤비 프로젝트 3주차 1회차 PR #237
Conversation
- User와 House의 관계는 Wish 이지, WishList 관계가 아니다.
- 사용자의 id와 집의 id 를 받아 찜목록에 추가한다.
- 사용자의 email을 인터셉터로부터 받아 이를 통해 찜 목록을 반환한다.
- 사용자의 id 와 wish의 id를 받아서 해당 wish 를 삭제한다.
- 파싱을 Bearer 로 하고 있었는데, bearer 로 해야한다.
- MySQL truncated error로 인한 longitude, latitude 자리 변경
- 환경 변수 yml로 변경 - Component Scan 및 DI 적용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 샤인 & 루시드, 리뷰어 dion 입니다.
변경량이 2천줄에 육박하네요. 읽는데 시간이 꽤 걸렸던 것 같습니다.
변경사항이 많은만큼 리뷰드린 내용도 많아졌습니다.
3번에서 중요한 점이 있죠. 우리가 스프링을 좀 더 공부를 해야하는 이유입니다.
만약에 너무 막히시면 DM이나 코멘트 주세요.
답변
- 좋은 로그는 어떤 것일지, 나쁜 로그는 어떤 것일지 한 번 생각해보시기 바랍니다.
catch
할 때 로그를 남기는 것은 좋은 로그일까요?- 실제로 로그를 봤을 때, 전달하고자 하는것이 쉽게 전달되는지 체크해보시기 바랍니다.
고생하셨습니다. 테스트 코드도 열심히 짜주셨네요 👍
마지막까지 지치지 않고 프로젝트 수행해주세요!
USER_NAME=$(env | grep username | cut -c 10-14) | ||
PASSWORD=$(env | grep password | cut -c 10-17) | ||
DATASOURCE=$(env | grep datasource | cut -c 12-97) | ||
GITHUB_CLIENT_SECRET=$(env | grep GITHUB_CLIENT_SECRET | cut -c 22-61) | ||
GITHUB_CLIENT_ID=$(env | grep GITHUB_CLIENT_ID | cut -c 18-37) | ||
KAKAO_CLIENT_ID=$(env | grep KAKAO_CLIENT_ID | cut -c 17-48) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 환경변수를 계속 추가하는 방식은 별로 좋지 못한 것 같네요. 계속 설정값이 추가될 때 마다 이렇게 해주실건가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
환경변수를 한번에 불러올 수 있는 방법이 따로 있는것 이군요?
그 혹시 죄송한데 힌트를 주실수 있을까요?
쉘스크립트 자체도 아직 어색하여서.... 이부분은 뭐랄까 "고민" 자체가 힘드네요.
Java, Spring은 어느정도 익숙은 하니까 말씀해주시는 부분을 고민하고 수정하는데...
쉘 스크립트 자체는 아직 미숙한 부분이 많아서.... 뭘 추가해야할지 모르겠습니다 ....
힌트를 주신다면 감사하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쉘 스크립트가 아닌 스프링 설정 파일에서 관리해주는 방법이 있지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그 server-url이나, redirect-url 같은 부분들은 스프링 설정파일로 관리하는데,
password 같은 부분은 Linux 환경변수 자체에 등록하여 사용하고 있습니다.
- password: ${password} 같이 설정파일에 지정후
- 리눅스 환경변수 로 설정
- 스크립트 상에서 실행할때 환경변수의 비밀번호를 찾아와서 커맨드로 추가
위와 같은 과정으로 진행되는것이 아닌가요?
설정 파일은 다음과 같이 사용중 입니다만...
spring:
profiles:
active: local
datasource:
url: ${datasource}
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${username}
password: ${password}
jwt:
token:
validate-time: 1800
token-secret: "Shine-Lucid"
kakao:
token-server-uri: "https://kauth.kakao.com/oauth/token"
oauth-server-uri: "https://kapi.kakao.com/v2/user/me"
client-id: ${KAKAO_CLIENT_ID}
redirect-uri: "http://3.38.93.166/api/login/oauth/kakao/callback"
github:
token-server-uri: "https://github.com/login/oauth/access_token"
oauth-server-uri: "https://api.github.com/user"
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_CLIENT_SECRET}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class GraphResponse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무슨 Graph 인지 특정되는게 좋지않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HousePriceGraph와 같은 이름으로 특정화하겠습니다!
@JsonDeserialize(using = LocalDateTimeDeserializer.class) | ||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmm") | ||
private LocalDateTime startDateTime; | ||
|
||
@JsonDeserialize(using = LocalDateTimeDeserializer.class) | ||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmm") | ||
private LocalDateTime endDateTime; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
가급적이면 다음부터는 ISO8601 형식을 지키는 형태로 디자인해주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 내용 배워갑니다!! 이런 부분들 저희 혼자 공부할때는 못느끼는 부분들인데 배워갑니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네! 변경하도록 하겠습니다.
@GetMapping("/reservation") | ||
public ReservationsResponse ListReservation(@RequestAttribute("userEmail") String userEmail) { | ||
return houseService.showReservations(userEmail); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 코드는 왜 이런 url을 선택하셨는지 궁금하네요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
유저의 예약리스트를 조회하는 API입니다.
예약 시 API는 POST : /api/houses/{houseId}/reservation
으로 수행했기 때문에 조회도 자연스럽게 houses에 속하도록 작성하였습니다.
조회 시에는 사실 User 정보 기반으로 조회되기 때문에 /api/users/reservation
으로 하는게 낫다는 생각도 듭니다.
이런 방식이 맞을까요? 아니면 api naming에 문제가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 좀 더 꼼꼼히 보니 메서드 네이밍 컨벤션도 문제가 있었군요.
제가 생각하는걸 좀 더 말씀드릴게요.
말씀하신대로, 후자의 의견을 저는 더 선호하는데요. 우리가 houses/reservations
라고 디자인하면 이건 일단 어색하지만, houses
의 예약 목록을 보여줄 것이라는 생각이 들 것 같아요.
그래서 자연스럽지 않다는 생각이 들었습니다.
public AccommodationCostResponse calculateFee(@PathVariable Long id, | ||
@RequestParam @DateTimeFormat(pattern = "yyyyMMddHHmm") LocalDateTime startDateTime, | ||
@RequestParam @DateTimeFormat(pattern = "yyyyMMddHHmm") LocalDateTime endDateTime) { | ||
log.info("[time] {}, {}", startDateTime, endDateTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
info 로그를 찍을만큼의 가치가 있었나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
데이터포맷 입력 테스트용으로 작성하였는데 불필요하니 삭제하도록 하겠습니다!
ReservationInformationRequest request = new ReservationInformationRequest( | ||
LocalDateTime.now(), LocalDateTime.now(), 1, 10000); | ||
|
||
Reservation reservation = new Reservation(request.getFee(), request.getNumberOfGuests(), | ||
request.getStartDateTime(), request.getEndDateTime(), null, house); | ||
|
||
given(houseRepository.findById(anyLong())) | ||
.willReturn(Optional.of(house)); | ||
|
||
given(userRepository.findUserByEmail(anyString())) | ||
.willReturn(Optional.of(user)); | ||
|
||
given(reservationRepository.save(any(Reservation.class))) | ||
.willReturn(reservation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
왜 모든 로직을 다 개행으로 분리해주셨나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
가독성을 위해 분리해주는 편입니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
가급적이면 연관성 있는 라인을 묶어주는 습관을 들여주세요.
빈 개행도 의미의 단위여서 가독성을 떨어뜨리는 효과를 줄 수 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네 알겠슴니다!
Assertions.assertThat(result.getHouseName()).isEqualTo("house1"); | ||
verify(houseRepository, times(1)).findById(anyLong()); | ||
verify(userRepository, times(1)).findUserByEmail(anyString()); | ||
verify(reservationRepository, times(1)).save(any(Reservation.class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 밖에 검증이 되지 않나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Transactional
public ReservationResponse reserveHouse(Long id, ReservationInformationRequest request, String userEmail) {
House house = houseRepository.findById(id)
.orElseThrow(() -> new IllegalStateException("찾을 수 없는 숙소 정보입니다."));
User host = userRepository.findUserByEmail(userEmail)
.orElseThrow(() -> new IllegalStateException("찾을 수 없는 유저입니다."));
Reservation reservation = new Reservation(
request.getFee(),
request.getNumberOfGuests(),
request.getStartDateTime(),
request.getEndDateTime(), host, house);
Reservation save = reservationRepository.save(reservation);
return new ReservationResponse(save);
}
- 테스트 대상 메서드를 살펴보면 결국 return되는 결과(ReservationResponse)는 Mocking되는 ReservationRepository의 save 메서드 반환 결과에 영향을 받는다고 생각되었습니다.
- 통합 테스트를 통해 Repository에서 받아오는 결과는 의미가 있지만, Mocking 하는 객체에서 반환되는 결과는 제가 결과를 테스트 코드 상에서 만들었다고(?) 생각했기에 큰 의미가 없으므로 result의 모든 것을 검증할 필요는 없다고 생각되었습니다.
- 다만 내부 로직 상 외부 메서드를 호출하는 부분(findById, findUserByEmail, save)은 예외 발생 시 호출되지 않을 수 있기 때문에 호출 여부를 검증해야한다고 생각하였습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any
로 검증해주시는게 정말 맞을까요?
호출만되면 OK인 코드였나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
생각해보니 Repository save 중 발생하는 예외도 고려해야겠네요..!
@DisplayName("token이 없는 회원이 wish list에 숙소를 추가하면 에러 메시지를 반환한다.") | ||
public void create_wish_list_error_test() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
무슨 에러 메시지를 반환하길 기대하나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IllegalStateException 예외 를 던지면 이를 catch하도록 테스트를 수정하였습니다.
지금 보니 예외를 발생시킨후, 예외가 발생하는지 "검증"을 안하고 있었습니다!
@DisplayName("가입한 회원은 위시 리스트 요청시 리스트를 반환한다.") | ||
public void get_wish_list_test() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트에 대한 의미가 전달이 잘 안됩니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 혹시 조금더 알려주실 수 있을까요?
사실 저부분을 작성한 저 스스로는 의미가 전달이 된다고 생각해서 저렇게 작성한 것이라 ㅠ,ㅠ
3자의 입장에서 어떤 부분이 불명확한지 알려주신다면 감사하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
가입한 회원이 왜 위시리스트 요청시에 리스트를 반환해야하나요?
뭔가 중간 과정은 건너뛰었다고 생각이 되지 않으실까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아.... "서버에서" 리스트를 반환해야 할것 같습니다.
어디서 반환을 해야하는지에 대한 부분이 빠져있었습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 제가 말씀드린 부분은 가입한 회원이 위시 리스트 요청시에 리스트를 반환한다는게 아니라
어떤 행동을 했을 경우에 리스트가 반환되지 않을까요? 직접 데이터를 밀어넣고 테스트하는 것 보단 행위 중심으로 테스트하시면 좋을 것 같아요.
.andExpect(jsonPath("valid").value("true")) | ||
.andExpect(jsonPath("message").value("OK")) | ||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_VALUE)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검증하고자 하는것이 응답만인가요? 아니면 응답 코드인가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상태코드 같은 경우 바로 위의 status().isOk() 를 통해 검증하고,
해당 message 부분같은 경우 응답 메시지를 검증하고 싶었습니다! 상태코드는 따로 위에서 검증하게 됩니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵넵 저라면 삭제하는 행위를 더 검증하고 싶었을 것 같네요. 😊
안녕하세요 Dion! 매번 저희 팀의 리뷰를 담당해 주셔서 감사의 인사를 전합니다!!
1. 구현 사항
이번에는 API를 구분하여 각각 구현해보았고, 추가된 부분은 다음과 같습니다.
2. 구현 예정
3. 특이사항
진행하면서 ObjectMapper의 Snake Case를 yml을 통해 전역으로 설정해줬습니다. 그러다보니 API 호출시에도 Snake Case가 적용되어 정상적으로 request를 처리하지 못하는 문제가 발생하였습니다. 이 부분을 Bean 생성으로 적용해보아도 동일한 문제가 발생하여 해결중에 있습니다.
요약)
4. 궁금한 점