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, 2단계 방탈출 예약 대기] 카피(김상혁) 미션 제출합니다. #17

Merged
merged 31 commits into from
May 19, 2024

Conversation

tkdgur0906
Copy link
Member

@tkdgur0906 tkdgur0906 commented May 15, 2024

안녕하세요 영이~!! 카피입니다!

이번 미션은 저의 코드로 이어서 진행했습니다!
잘 부탁드립니다ㅎㅎ

이번 미션 변경사항

링크

이전의 웹 개발 경험

스프링을 통한 프로젝트 개발 경험이 있습니다.
JPA도 사용해본 적이 있지만 흐릿한 기억으로 남아있습니다.
AWS를 통해 배포해본 적이 있습니다.

웹 테스팅 시 로그인 정보

일반 회원

관리자

Minjoo522 and others added 13 commits May 14, 2024 15:25
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Co-authored-by: tkdgur0906 <tkdgur0906@naver.com>
Copy link

@choijy1705 choijy1705 left a comment

Choose a reason for hiding this comment

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

안녕하세요 카피
미션 빠르게 진행해주셨네요👍
몇가지 코멘트 남겼으니 확인부탁드립니다!

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {

Choose a reason for hiding this comment

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

Suggested change
Object handler) throws Exception {
Object handler) {

Comment on lines 20 to 28
@GetMapping("/members")
public ResponseEntity<List<MemberIdAndNameResponse>> getMembers() {
List<Member> members = memberService.findMembers();
return ResponseEntity.ok(
members.stream()
.map(member -> new MemberIdAndNameResponse(member.getId(), member.getName()))
.toList()
);
}

Choose a reason for hiding this comment

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

이 api는 어디서 호출되는건가요??

Copy link
Member Author

@tkdgur0906 tkdgur0906 May 16, 2024

Choose a reason for hiding this comment

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

관리자 예약 페이지에서 관리자가 직접 예약을 추가할 시 선택할 멤버의 목록을 가져올 때 사용되는 api입니다.
전체 사용자의 목록을 일반 유저가 볼 수 있으면 안되기 때문에 interceptor의 config에 해당 api를 추가해놓았습니다.

reservation-with-member.js에서 확인하실 수 있습니다!

현재 엔드포인트를 "/admin/members"로 변경했습니다!

Cookie[] cookies = request.getCookies();
String token = tokenProvider.extractTokenFromCookie(cookies);

if ("".equals(token)) {

Choose a reason for hiding this comment

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

Suggested change
if ("".equals(token)) {
if (token.isEmpty()) {

extractTokenFromCookie에서 "" 이 반환되면 안되고 null이 반환되어야 할 것 같네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

전체적인 flow에서 토큰이 없다면 null을 반환하도록 변경하였습니다!
해당 로직은 null과 isEmpty()를 둘다 검증하도록 했습니다!

Comment on lines 49 to 58
public String extractTokenFromCookie(Cookie[] cookies) {
if (cookies == null) {
return "";
}
return Arrays.stream(cookies)
.filter(cookie -> TOKEN.equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst()
.orElse("");
}

Choose a reason for hiding this comment

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

빈문자열을 반환하기보다는 null을 반환하는게 좋을것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

위와 동일하게 null을 반환하도록 했습니다!

}

public Long parseToken(String token) {
if (token == "") {

Choose a reason for hiding this comment

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

문자열 == 비교가 유효한가요??

Copy link
Member Author

Choose a reason for hiding this comment

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

해당 비교는 주소값을 비교하는 것이기 때문에 정상적으로 작동하지 않습니다.
따라서 equals() 또는 isEmpty()를 사용해야합니다!

isEmpty()로 변경하였습니다!

);
}

@GetMapping("/admin/reservations/search")

Choose a reason for hiding this comment

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

admin을 위한 api라면 별도의 controller로 관리되는게 좋아보이네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

동의합니다! admin 예약 전용과 일반 예약을 구분하기 위해 AdminReservationApicontroller를 새로 생성하였습니다

Comment on lines 24 to 30
public Member(Long id, String name, String email, String password, Role role) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
this.role = role;
}

Choose a reason for hiding this comment

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

도메인에 대한 validation은 필요없을까요?
다른 도메인들도 고려해봐주세요!

Copy link
Member Author

@tkdgur0906 tkdgur0906 May 17, 2024

Choose a reason for hiding this comment

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

email은 이메일 형식에 대한 검증이 필요할 것 같아 Email 객체로 wrapping하였습니다.
하지만 password나 다른 모든 필드에 대해 객체로 감싸는 것은 불필요한 것 같아 하지 않았습니다. (하나의 도메인으로써의 역할이 부족하다고 생각했습니다)

Member를 기준으로 봤을 때 지금 검증할만한 필드는 name, password의 null 체크 정도인 것 같은데, request로 받을 때 @NotNull 을 사용해서 검증하고 있기 때문에 굳이 필요한가? 라는 생각이 들었습니다.
도메인 단의 검증과 dto단의 검증은 다르다는 말도 들었지만 아직 명확한 기준이 서지 않아 영이의 생각을 묻고 싶습니다!

Choose a reason for hiding this comment

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

  1. dto의 검증은 빠른 에러를 내보내기 위한 검증입니다. 비즈니스 로직이 복잡한경우 몇초가 걸리는 경우도 있습니다. 이때 로직이 처리된후 에러가 발생하면 고객의 입장에서는 굉장히 짜증날것 같네요
  2. 도메인의 검증은 로직이 수행되고 실제 도메인이 생성될때의 검증입니다. dto로 고객의 입력값으로 도메인이 생성될 수 있지만 내부적인 로직으로 인해 값이 변경될 수 있고. 여러가지 이유에서 dto와는 상관없는 값이 도메인으로 만들어질 수 있기 때문에 반드시 도메인의 검증은 필요할것같습니다

Copy link
Member Author

Choose a reason for hiding this comment

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

dto의 검증과 도메인의 검증이 맡는 역할이 다른 것 같습니다!
dto에서는 요청에 대한 검증을 하고, 도메인에서는 생성 그 자체에 대한 검증을 진행하는군요
현재는 단순하게 검증을 했는데, 도메인이 확장되면 VO로 분리해서 검증 책임을 나누는 것도 좋을 것 같습니다!

private LocalDate date;

@ManyToOne
private ReservationTime time;

Choose a reason for hiding this comment

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

컬럼명이 time_id로 들어갈것 같은데
도메인이 많아지면 정확한 명칭이 아니면 큰 혼란을 야기할 수 있을것 같습니다
모델이 reservationTime이고 테이블명도 reservationTime이기 때문에 reservationTimeId로 저장되도록 하는게 좋을것 같아요🙏

Copy link
Member Author

Choose a reason for hiding this comment

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

임의로 줄여쓰게 된다면 나중에 헷갈릴 수 있을 것 같네요!
명확히 필드 이름을 reservationTime으로 수정했습니다!

Comment on lines 59 to 63
.keySet()
.stream()
.map(reservationTime -> new ReservationStatusResponse(
reservationTime,
reservationStatus.findReservationStatusBy(reservationTime))

Choose a reason for hiding this comment

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

entrySet을 사용하면
reservationStatus.findReservationStatusBy 메서드도 지우고 더 가독성 좋아질것 같네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

key를 가져오는게 아니라 그냥 entrySet을 가져오면 되는군요😅
훨씬 가독성이 좋은 것 같습니다!

@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne

Choose a reason for hiding this comment

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

lazy laoding에 대한 부분은 고려해보신걸까요?

Copy link
Member Author

@tkdgur0906 tkdgur0906 May 17, 2024

Choose a reason for hiding this comment

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

기존에 즉시 로딩에 대한 문제점은 들어본적이 있지만, 당시에 학습이 안된 상태여서 적용하지 않았습니다!
오늘 jpa 수업도 듣고 혼자 공부해보며 LAZY를 사용하게 되는 이유를 알게되었습니다.

제가 내린 결론은 조회시 불필요한 객체를 모두 가져오게 될 수 있으므로 EAGER 대신 LAZY + Fetch Join을 사용하자! 입니다.
여기서 고민해본 지점이 그럼 Inner join은 사용하지 않는 것인가? 였습니다.
현재 @Query를 사용하고 있는 2가지 메서드에서 join 대상은 검색 조건으로만 사용하고 반환하지는 않습니다.
따라서 불필요하게 영속성 컨텍스트에 올리지 않기 위해 inner join을 사용하는 것이 좋다 라는 결론을 냇는데, 영이의 생각은 어떠신가요?

Choose a reason for hiding this comment

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

어디가 좋다 나쁘다라는 걸 단정지을수 없는 부분인것 같아요!
상황에따라서 적절히 선택하는것이 좋을것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

말씀하신대로 fetch join과 inner join을 상황에 맞게 적절히 사용하는게 좋은 것 같습니다!

@tkdgur0906
Copy link
Member Author

안녕하세요 영이~
리뷰 반영하면서 궁금한 것이 생겨 질문드립니다!

JPA query method vs @Query

메서드 이름을 가지고 쉽게 query method를 작성할 수 있는 것이 상당히 편리하다고 생각합니다.
하지만 요구 조건이 늘어남에 따라 메서드 이름의 길이가 길어지는 경우 @Query를 사용하여 작성하는게 수고는 들지만 사용할때 명확하다고 생각합니다.
현재 사용중인 existsByDateAndReservationTimeIdAndThemeId 도 개인적으로 너무 길지 않나? 라는 생각이 드는데,
영이는 본인만의 기준을 가지고 계신가요?

Copy link

@choijy1705 choijy1705 left a comment

Choose a reason for hiding this comment

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

카피 피드백 잘 반영해주셨습니다👍
한번만 더 피드백 반영 요청 드려요!
확인 해보시고 반영부탁드립니다

spring.datasource.url=jdbc:h2:mem:database
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.ddl-auto=create-drop

Choose a reason for hiding this comment

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

개발환경은 로컬에서 코드를 작성하실때를 의미하는 것입니다!
코드를 작성하고 pr을 작성하는 코드는 실제 프로덕션까지 가는 코드입니다

jpa 가 자동으로 테이블을 생성하는걸 프로덕션에 그대로 쓰면 어떤 문제가 있을지는 한번 공부해보시면 좋을것 같아요
너무 많은 이유가 있어서 코멘트로 남길수 없을것 같습니다🙏

Comment on lines 24 to 30
public Member(Long id, String name, String email, String password, Role role) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
this.role = role;
}

Choose a reason for hiding this comment

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

  1. dto의 검증은 빠른 에러를 내보내기 위한 검증입니다. 비즈니스 로직이 복잡한경우 몇초가 걸리는 경우도 있습니다. 이때 로직이 처리된후 에러가 발생하면 고객의 입장에서는 굉장히 짜증날것 같네요
  2. 도메인의 검증은 로직이 수행되고 실제 도메인이 생성될때의 검증입니다. dto로 고객의 입력값으로 도메인이 생성될 수 있지만 내부적인 로직으로 인해 값이 변경될 수 있고. 여러가지 이유에서 dto와는 상관없는 값이 도메인으로 만들어질 수 있기 때문에 반드시 도메인의 검증은 필요할것같습니다


ADMIN,
USER
;

Choose a reason for hiding this comment

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

Suggested change
;

이런 컨벤션은 어느 회사도 안쓸것 같아요!
인텔리제이에서도 회색처리되고 있어서 안쓰면 좋을것 같습니다

@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne

Choose a reason for hiding this comment

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

어디가 좋다 나쁘다라는 걸 단정지을수 없는 부분인것 같아요!
상황에따라서 적절히 선택하는것이 좋을것 같아요

Optional<ReservationTime> findByStartAt(LocalTime startAt);

@Query("""
SELECT rt

Choose a reason for hiding this comment

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

Suggested change
SELECT rt
SELECT rt.id

로직상 id만 받아되 되지 않나 싶네요!

Copy link
Member Author

@tkdgur0906 tkdgur0906 May 18, 2024

Choose a reason for hiding this comment

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

ReservationTimeApiControllergetReservationTimesIsBooked()에서 response를 보낼 때 startAt을 같이 보내기 때문에 객체 전체의 값이 필요할 것 같습니다!

GROUP BY t.id
ORDER BY COUNT(t.id) DESC
""")
List<Theme> findRanksByPeriodAndCount(@Param("dateFrom") LocalDate dateFrom,

Choose a reason for hiding this comment

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

jpa가 생성해주는게 아닌 쿼리를 명시해주는 경우 메서드 명을 좀더 유연하게 작성할 수 있을것 같아요
findPopularTheme? 같은 메서드명이 더 가독성이 좋지 않을까요?

Copy link
Member Author

@tkdgur0906 tkdgur0906 May 18, 2024

Choose a reason for hiding this comment

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

쿼리를 직접 작성했기 때문에 메서드 명을 유연하게 작성할 수 있을 것 같습니다!
findPopularThemes로 변경했습니다!

Comment on lines 26 to 41
@GetMapping("/admin/members")
public ResponseEntity<List<MemberIdAndNameResponse>> getMembers() {
List<Member> members = memberService.findMembers();
return ResponseEntity.ok(
members.stream()
.map(member -> new MemberIdAndNameResponse(member.getId(), member.getName()))
.toList()
);
}

@PostMapping("/members")
public ResponseEntity<MemberIdAndNameResponse> signup(@RequestBody @Valid SignupRequest request) {
Member member = memberService.signUp(request);
return ResponseEntity.created(URI.create("/members/" + member.getId()))
.body(new MemberIdAndNameResponse(member.getId(), member.getName()));
}

Choose a reason for hiding this comment

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

admin 과 일반 api 는 클래스 분리가 되면 좋을것 같아요


List<Reservation> findByMemberId(long memberId);

boolean existsByDateAndReservationTimeIdAndThemeId(LocalDate date, long timeId, long themeId);

Choose a reason for hiding this comment

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

메서드 이름을 가지고 쉽게 query method를 작성할 수 있는 것이 상당히 편리하다고 생각합니다.
하지만 요구 조건이 늘어남에 따라 메서드 이름의 길이가 길어지는 경우 @query를 사용하여 작성하는게 수고는 들지만 사용할때 명확하다고 생각합니다.
현재 사용중인 existsByDateAndReservationTimeIdAndThemeId 도 개인적으로 너무 길지 않나? 라는 생각이 드는데,
영이는 본인만의 기준을 가지고 계신가요?

딱히 기준같은건 없습니다. @Query 를 사용한다고해서 그렇게 명확해지는지에 대해서는 의문입니다🤔
그래도 jpa의 메서드 명 규칙이 더 이해하기 쉬울것 같긴하네요 개인적으로는
파라미터가 일정 수준 이상 넘어가게되면 테이블의 설계가 문제 이거나 혹은 로직적으로 푸는 방법을 고려해봐야하지 않을가 싶습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

그런 관점으로도 볼 수 있겠군요😀
이 부분에 대한 것은 미션을 진행하면서 저만의 기준을 세워봐야겠습니다!

Copy link

@choijy1705 choijy1705 left a comment

Choose a reason for hiding this comment

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

카피 피드백 반영 잘해주셨습니다👍
이번단계는 머지할께요
다음 단계 진행해주세요!

@choijy1705 choijy1705 merged commit fcf4785 into woowacourse:tkdgur0906 May 19, 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.

4 participants