-
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
[3 - 4단계 방탈출 예약 대기] 몰리(김지민) 미션 제출합니다. #145
Conversation
- MemberIntegrationTest 테스트 개선
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.
몰리 안녕하세요~
이전 코드까지 꼼꼼히 리팩토링해주셨군요! 수정해주신 부분과 waiting 테이블 분리에 대해서도 코멘트 드렸습니다 :) 확인해보시고 코멘트로 이어서 얘기 나눠보면 좋을 것 같아요!
private LocalDateTime createdAt; | ||
|
||
@LastModifiedDate | ||
private LocalDateTime modifiedAt; |
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.
Auditing 기능까지 잘 구현해주셨네요 👍 두 필드에 제약 사항은 없을까요?
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.
찾아보니 updatable
이라는 조건이 있더라구요!
생성 시간은 변경되지 않아야 하는 값이므로 해당 조건이 필요할 것 같습니다!
추가로, 두 필드에 대해서 not null
이라는 조건이 필요할까? 라는 생각을 해보았는데요.
저는 해당 필드들은 디버깅을 위한 로깅용 필드라고 생각했어요.
그런데 not null 조건이 붙게 되면, null일 때 예외가 터져야 하는데 이 예외가 "사용자 입장에서 본다면, 예외가 터지는 게 뭔가 어색하지 않나?"라는 생각을 했습니다.
(물론 클라이언트 측에서 적절한 문구나 재시도를 하겠지만 어쨋든 사용자의 동작에 영향이 갈 것이라고 생각했습니다)
따라서 없어도 괜찮을 것 같은 조건일 것 같다고 생각을 했었어요.
그런데 크루들과 이야기하다보니, 앞의 말처럼 해당 조건은 디버깅을 위한, 즉 개발자를 위한 조건이다. 어떤 것은 null이고 어떤 시간은 null이 아니라면, 일관성이 적절하지 않고 따라서 그에 대한 추가 처리가 필요해질 수도 있다는 이야기를 들었고 납득을 했던 것 같아요.
이에 대해서 혹시 다른 의견이 있으시다면 궁금합니다..!
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.
네 updatable까지 잘 찾아보셨네요 👍 저도 NotNull로 처리하는 걸 추천드려요. BaseEntity를 상속받았다는 건 어쨌든 auditing을 사용하겠다, 관련 필드를 가진다라는 의미인데 테이블에 createdAt이 null로 들어가있다면 굉장히 의아할 것 같아요. 이런 부분은 일괄적으로 통일해서 관리하는 게 좋습니다.
@@ -8,4 +9,13 @@ | |||
public interface MemberRepository extends JpaRepository<Member, Long> { | |||
|
|||
Optional<Member> findByEmail(Email email); | |||
|
|||
default Member getByEmail(Email email, String exceptionMessage) { |
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.
member를 조회하는데 예외 메시지도 넣어줘야 하나요? 차라리 findByEmail을 쓸까요?
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.
앗 이 부분은 잘못 남아있었던 것 같아요 🥲
예외 메시지를 외부에서 받을 필요는 없을 것 같습니다... 변경하겠습니다 !ㅎㅎ
} | ||
|
||
@Test | ||
@DisplayName("같은 예약 시간 확인: 참") |
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 org.springframework.test.context.TestContext; | ||
import org.springframework.test.context.support.AbstractTestExecutionListener; | ||
|
||
public class DatabaseCleaner extends AbstractTestExecutionListener { |
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.
jdbctemplate 사용하여 db를 초기화하도록 DatabaseCleaner까지 구현해주셨군요 👍
|
||
@Service | ||
public class ReservationService { | ||
|
||
private final WaitingService waitingService; |
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.
ReservationService
에서 WaitingService
를 의존하도록 한 이유는 ReservationService에서 Waiting객체를 조작하는 것이 어색해보였던 게 컸었어요.
다른 도메인에 대한 단순 조회(read) 행위는 해당 repository를 바로 의존해도 문제가 없을 것 같은데,
다른 도메인의 변경이나 생성(write)등에 대한 행위는 해당 도메인의 Service에서만 처리를 하는 게 맞는 것 같다는 생각을 했어요.
한 도메인에 대한 write에 행위가 여러 곳에서 퍼져있다면, 해당 객체의 상태가 어떻게, 어디서 변경되었는지 알 수 없어서 관리하기에도 어려울 것 같다고 생각했습니다...!
그런데, 위에서 계속 파랑이 말씀하신 것처럼 언젠가 서로 의존하게 되는 문제점이 있을 것 같아요. 일단 ReservationWaitingService처럼 변경해볼 수 있을 것도 같은데, 동시에 어느 도메인의 하위에 있냐라고 묻는 다면 사실 잘 모르겠어요...
이런 한 도메인의 행위 중에 다른 도메인의 상태를 변경해야 하는 경우에 보통 파랑은 어떻게 처리하시는지 듣고 싶습니다. 🥲
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.
repositoy만 의존한다면 검증도 많고 로직이 복잡한 생성이나 수정의 경우에 코드가 여러 곳으로 흩어집니다. 그럼 중복이 발생하고 관리가 어렵겠죠. 단점은 말씀해주신 것처럼 순환참조일테구요ㅎㅎ
예약은 예약 시간, 테마, 회원 도메인을 참조하지만 예약 시간, 테마, 회원은 예약을 참조하지 않는다 처럼 도메인 간의 참조 방향을 잘 구성해두었다면 순환참조가 문제되지 않을 수도 있는데요, 그렇지 않은 경우 서비스도 계층화 시킬 수 있습니다. 보통 파사드 패턴이라 부르더라구요. 서비스를 계층화시키고 상위 서비스에서 하위 서비스만 의존하는 방식으로 구현하면 순환참조를 없애고 서비스를 사용할 수 있습니다. 물론 미션에서 적용하기엔 과하다고 생각하지만요 ㅎㅎ. 저의 팀도 이런 방식을 사용하고 있습니다.
public void deleteWaiting(final Long waitingId) { | ||
Waiting waiting = waitingRepository.getById(waitingId); | ||
waitingRepository.delete(waiting); | ||
} | ||
|
||
public void deleteWaiting(final AuthInfo authInfo, final Long waitingId) { |
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.
WaitingService를 사용할 때 메서드 시그니처만 보면 두 개의 deleteWaiting 메서드를 구분할 수 있을까요?
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.
메서드 이름을 조금 더 구별할 필요가 있을 것 같아요 🥲
수정하겠습니다!
|
||
public void rejectWaiting(final Long waitingId) { | ||
waitingRepository.findById(waitingId) | ||
.ifPresentOrElse(waitingRepository::delete, () -> { |
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.
취향 차이가 있겠지만 개인적으로 ifPresentOrElse
구문이 가독성이 좋아 보이진 않네욥 ㅎㅎ
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.
동의합니다...! ifPresentOrElse
를 많이 사용해보지 않아서 써보고 싶었나봐요...ㅎㅎ
추가로, 해당 메서드는 기존 deleteWaiting 메서드를 재사용해도 될 것 같아서, 해당 메서드 제거하고 변경했습니다!
} | ||
} | ||
|
||
public void rejectWaiting(final Long waitingId) { |
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.
deleteWaiting과 rejectWaiting의 차이는 무엇인가요?
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.
위의 코멘트에서 deleteWaiting
메서드와 하는 일이 동일하고 rejectWaiting
라는 이름이 오히려 사용자의 한 행위에 대해서만 종속적인 이름인 것 같아서 제거했습니다 !
reservation.updateMember(waiting.getMember()); | ||
waitingService.deleteWaiting(waiting.getId()); |
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.
waiting을 분리했으니 한 편으로는 변경에 유연해진 게 맞아요. 예약 시에 추가로 입력해야 하는 정보가 생긴하면 waiting 테이블에 컬럼을 추가하면 간단히 해결 되겠죠!
반면 다른 방향으로 생각해보면 우리가 예약 내역을 확인할 때 취소한 예약은 아예 노출하지 않을 수도 있지만, 예약은 그대로 존재하고 상태만 [취소된 예약]으로 보이게 만들기도 합니다. 이런 요구사항이 들어올 수도 있다고 생각하면, 기존 예약 삭제 시 다음 예약자로 member를 update 치는 방식은 '취소된 예약도 확인할 수 있다'는 요구사항을 만족시키기는 어려워보입니다.
추가로 대기 상태를 포함한 모든 예약을 조회(혹은 검색)하기도 어려워 보여요. 특히나 페이지네이션이 들어간다면 더더욱 구현하기 어려울 것 같구요. 테이블을 분리했다는 건 그만큼 예약과 예약 대기를 별개로 보겠다는 의미가 강해집니다. 서비스에서 둘을 아예 분리해서 취급하는 경우가 많다면 굉장히 유용해질 거고 두 개를 합쳐서 취급하는 경우가 많다면 오히려 조회가 어려워질 수도 있어요. 몰리는 어떤 것 같나요?
실제로 이런 기능은 필요해보이는데 곧 들어오겠지? 하고 예측해서 만들었는데 전혀 사용되지 않기도 하고, 전혀 고려하지 않았던 요구사항이 들어오기도 합니다ㅎㅎ. 어디까지 예측해서 설계할지 항상 고민되는 부분입니다 🤔
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.
예약은 그대로 존재하고 상태만 [취소된 예약]으로 보이게 만들기도 합니다.
추가로 대기 상태를 포함한 모든 예약을 조회(혹은 검색)하기도 어려워 보여요. 특히나 페이지네이션이 들어간다면 더더욱 구현하기 어려울 것 같구요. 테이블을 분리했다는 건 그만큼 예약과 예약 대기를 별개로 보겠다는 의미가 강해집니다. 서비스에서 둘을 아예 분리해서 취급하는 경우가 많다면 굉장히 유용해질 거고 두 개를 합쳐서 취급하는 경우가 많다면 오히려 조회가 어려워질 수도 있어요. 몰리는 어떤 것 같나요?
취소된 예약도 확인할 수 있다
에 대해서는 ReservationArchive
처럼 백업용 테이블을 만들면 되지 않을까 하고만 생각을 했었는데, 파랑의 예시처럼 합쳐서 조회하는 경우에는 그렇게 된다면 더더욱 조회가 어려워질 것 같네요 🥲
또 구현을 하면서 저는 처음부터 예약과 대기는 다른 API로 조회할 일이 많을 것 같다고 너무 멀리 예상해서, 이번 View page를 탓했는데 현재 요구사항에서는 다른 API로 조회 보다는 삭제된 예약도 추가한 예약 조회 API가 현재의 요구사항에서는 가깝다고도 생각이 들기도 해요. 오히려 합치는 것이 맞았을 지도 모른다는 생각도 드네요...
정말 너무 어려운 부분 같습니다 😭😭😭
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.
그렇죠 🥹 이런 경우에 수정이 쉬운 쪽으로 가는 것도 방법입니다. 테이블을 분리했는데 나중에 합치는 것 VS 테이블을 하나로 사용했는데 나중에 분리하는 것 어떤 방법이 쉬울지 고민해보고 쉬운 쪽으로 가기도 합니다 ㅋㅋ.. 아니면 먼 미래는 고민하지 않고 현 상황에 최선인 방법을 선택하는 것도 방법이구요.
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.
몰리 안녕하세요~
마지막까지 수고하셨습니다 👏 더 궁금한 점 있으면 DM으로 질문주세요. 충분히 리팩토링된 것 같아서 방탈출 예약 대기 미션은 여기서 머지하겠습니다. 레벨2도 이제 막바지에 왔네요 끝까지 파이팅입니다~~
private LocalDateTime createdAt; | ||
|
||
@LastModifiedDate | ||
private LocalDateTime modifiedAt; |
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.
네 updatable까지 잘 찾아보셨네요 👍 저도 NotNull로 처리하는 걸 추천드려요. BaseEntity를 상속받았다는 건 어쨌든 auditing을 사용하겠다, 관련 필드를 가진다라는 의미인데 테이블에 createdAt이 null로 들어가있다면 굉장히 의아할 것 같아요. 이런 부분은 일괄적으로 통일해서 관리하는 게 좋습니다.
|
||
@Service | ||
public class ReservationService { | ||
|
||
private final WaitingService waitingService; |
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.
repositoy만 의존한다면 검증도 많고 로직이 복잡한 생성이나 수정의 경우에 코드가 여러 곳으로 흩어집니다. 그럼 중복이 발생하고 관리가 어렵겠죠. 단점은 말씀해주신 것처럼 순환참조일테구요ㅎㅎ
예약은 예약 시간, 테마, 회원 도메인을 참조하지만 예약 시간, 테마, 회원은 예약을 참조하지 않는다 처럼 도메인 간의 참조 방향을 잘 구성해두었다면 순환참조가 문제되지 않을 수도 있는데요, 그렇지 않은 경우 서비스도 계층화 시킬 수 있습니다. 보통 파사드 패턴이라 부르더라구요. 서비스를 계층화시키고 상위 서비스에서 하위 서비스만 의존하는 방식으로 구현하면 순환참조를 없애고 서비스를 사용할 수 있습니다. 물론 미션에서 적용하기엔 과하다고 생각하지만요 ㅎㅎ. 저의 팀도 이런 방식을 사용하고 있습니다.
reservation.updateMember(waiting.getMember()); | ||
waitingService.deleteWaiting(waiting.getId()); |
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.
그렇죠 🥹 이런 경우에 수정이 쉬운 쪽으로 가는 것도 방법입니다. 테이블을 분리했는데 나중에 합치는 것 VS 테이블을 하나로 사용했는데 나중에 분리하는 것 어떤 방법이 쉬울지 고민해보고 쉬운 쪽으로 가기도 합니다 ㅋㅋ.. 아니면 먼 미래는 고민하지 않고 현 상황에 최선인 방법을 선택하는 것도 방법이구요.
|
||
@RestController | ||
@RequestMapping("/admin/reservations") | ||
public class AdminReservationController { |
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.
reservation으로 옮겨졌네요 👍
안녕하세요 파랑 🐬
오랜만에 리뷰 요청드립니다. 이것저것 수정하다보니 늦어졌네요 🥲
참고하시면 좋을 부분 첨부합니다!
정책
예약 대기 정책
예약 취소 정책
질문
ERD
저는 예약과 대기는 다른 개념이라고 보아서, 예약에 대한 waiting 테이블을 추가했는데요!
reservation이 삭제될 경우, 대기의 예약 자동 확정은 다음 분기로 나누어집니다.
기존 reservation 테이블에 대해 변경을 최대한 줄이고, 대기 기능을 구현한 것 같아서 나름 만족했습니다.
(동시성 부분에서는 고려해야할 것들이 있을 것 같습니다 🤔)하지만 다른 변경 사항에 대해서도 유연한 코드인지는 모르겠어요.
매번 테이블을 생성해서 처리해도 될까? 라는 생각도 들지만, 동시에 현재 규모에서 안될 이유도 없다는 생각도 듭니다.
혹시 이 구조에 대해 우려되는 점들을 들어볼 수 있을까요 ?
이번 리뷰도 잘 부탁드립니다 🙇