-
Notifications
You must be signed in to change notification settings - Fork 0
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
Bp 25 implement user registration #6
Changes from 8 commits
99546f7
3e330f6
66e3696
f396c5d
d66dfe4
6d4cd4d
083a6db
b64fae8
630a7c8
83f4b5c
86e3842
3a2b5d2
650e1b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package gdsc.konkuk.platformcore.application.member; | ||
|
||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
import gdsc.konkuk.platformcore.domain.member.entity.MemberRole; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Builder | ||
public class MemberRegisterRequest { | ||
private String memberId; | ||
private String password; | ||
private String name; | ||
private String email; | ||
private MemberRole memberRole; | ||
private int batch; | ||
|
||
public static Member toEntity(MemberRegisterRequest request) { | ||
return Member.builder() | ||
.memberId(request.getMemberId()) | ||
.password(request.getPassword()) | ||
.name(request.getName()) | ||
.email(request.getEmail()) | ||
.role(request.getMemberRole()) | ||
.batch(request.getBatch()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package gdsc.konkuk.platformcore.application.member; | ||
|
||
import java.util.Optional; | ||
|
||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException; | ||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; | ||
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; | ||
import gdsc.konkuk.platformcore.global.responses.SuccessResponse; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (readOnly = true)의 장점은 무엇일까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 읽기 전용으로 트랜잭션을 설정하여 어느정도 성능의 이점을 가져갈 수 있습니다. 대표적으로 데이터 변경이 일어나지 않는것을 보장하므로 스냅샷을 저장하지 않죠(변경감지가 필요없어요). |
||
public class MemberService { | ||
|
||
private final PasswordEncoder passwordEncoder; | ||
private final MemberRepository memberRepository; | ||
|
||
private boolean checkMemberAlreadyExist(String memberId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private 메서드는 public 메서드 보다 아래쪽에 나와야 합니다!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private를 아래로 내리겠습니다! |
||
Optional<Member> member = memberRepository.findByMemberId(memberId); | ||
return member.isPresent(); | ||
} | ||
|
||
@Transactional | ||
public SuccessResponse register(MemberRegisterRequest registerRequest) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. register 메서드는 SuccessResponse에 의존적이게 되었군요, 향후 다른 응답이 필요하면 해당 부분을 변경해야 할것 같은데요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 서비스단에서는 객체만 반환을 하고 프레젠테이션 레이어에서 응답객체를 만들어 반환하도록 하는것을 말씀하시나용? |
||
|
||
if(checkMemberAlreadyExist(registerRequest.getMemberId())) { | ||
throw UserAlreadyExistException.of(ErrorCode.USER_ALREADY_EXISTS); | ||
} | ||
|
||
Member member = Member.builder() | ||
ekgns33 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.memberId(registerRequest.getMemberId()) | ||
.password(passwordEncoder.encode(registerRequest.getPassword())) | ||
.name(registerRequest.getName()) | ||
.email(registerRequest.getEmail()) | ||
.role(registerRequest.getMemberRole()) | ||
.batch(registerRequest.getBatch()) | ||
.build(); | ||
|
||
memberRepository.save(member); | ||
|
||
return SuccessResponse.messageOnly(); | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more.
아래는 @ExceptionHandler(UserAlreadyExistException.class)
protected ResponseEntity<ErrorResponse> handleUserAlreadyExistException(final UserAlreadyExistException e) {
log.error("UserAlreadyExistException Caught! [{}]", e.getLogMessage());
final ErrorResponse response = ErrorResponse.of(e.getMessage(), e.getLogMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessException e) {
log.error("BusinessException Caught! [{}]", e.getLogMessage());
final ErrorResponse response = ErrorResponse.of(e.getMessage(), e.getLogMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
그래서 떠오른 방법에 대한 @ekgns33의 의견이 궁금합니다!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1번으로 가는게 어떨까요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인터넷 오류였을까요...? 분명 전 하나의 답글을 보고 2번째 comment를 작성했는데 이제보니 2개네요??? 말씀하신것처럼 2번째 comment에서 작성한 떠오른 방법은 1, 2, 3 중에서 하나를 선택하자가 아닌 순서대로 전부라는 의미였는데, 지금 다시 읽어보니 뭔가뭔가인 느낌이 있네요. 해당 사항은 저도 조금 더 고민해 보겠습니다. 1번을 말씀하셨는데, 떠오른 좋은 구조가 있다면 편하신대로 re-factoring을 진행해주시면 감사합니다. 그런데 만약 저 3중에 하나를 골라야 한다고 생각하셨다면 전혀 아니니, 더 나은 구조가 없다면 현상 유지도 좋다고 생각합니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package gdsc.konkuk.platformcore.application.member.exceptions; | ||
|
||
import gdsc.konkuk.platformcore.global.exceptions.BusinessException; | ||
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; | ||
|
||
public class UserAlreadyExistException extends BusinessException { | ||
|
||
protected UserAlreadyExistException(ErrorCode errorCode, String logMessage) { | ||
super(errorCode, logMessage); | ||
} | ||
|
||
public static UserAlreadyExistException of(ErrorCode errorCode) { | ||
return new UserAlreadyExistException(errorCode, errorCode.getLogMessage()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package gdsc.konkuk.platformcore.controller.member; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import gdsc.konkuk.platformcore.application.member.MemberRegisterRequest; | ||
import gdsc.konkuk.platformcore.application.member.MemberService; | ||
import gdsc.konkuk.platformcore.global.responses.SuccessResponse; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/members") | ||
@RequiredArgsConstructor | ||
public class MemberController { | ||
|
||
private final MemberService memberService; | ||
|
||
@PostMapping("") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ("") 이부분 없어도 됩니다! |
||
public ResponseEntity<SuccessResponse> signup(@RequestBody MemberRegisterRequest registerRequest) { | ||
memberService.register(registerRequest); | ||
return ResponseEntity.status(201).body(SuccessResponse.messageOnly()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 201같은 상수를 직접 사용하기 보다는, HttpStatus 에 정의된 상수값을 사용하길 권장합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 매직넘버를 제거하고 |
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException; | ||
import gdsc.konkuk.platformcore.global.exceptions.BusinessException; | ||
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; | ||
import gdsc.konkuk.platformcore.global.responses.ErrorResponse; | ||
|
@@ -14,6 +15,12 @@ | |
@RestControllerAdvice | ||
public class GlobalExceptionHandler { | ||
|
||
@ExceptionHandler(UserAlreadyExistException.class) | ||
protected ResponseEntity<ErrorResponse> handleUserAlreadyExistException(final UserAlreadyExistException e) { | ||
log.error("UserAlreadyExistException Caught! [{}]", e.getLogMessage()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. log.error("UserAlreadyExistException Caught! [{}]", e.getLogMessage(), e); 끝에 e도 인자로 추가하면 stack trace도 모두 출력됩니다. 추가적인 e를 위한 {}는 지정하지 않아도 되구요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런 방법이 있군요! 적용해보겠습니다🤗 |
||
final ErrorResponse response = ErrorResponse.of(e.getMessage(), e.getLogMessage()); | ||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); | ||
} | ||
|
||
@ExceptionHandler(BusinessException.class) | ||
protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessException e) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,9 @@ public enum ErrorCode { | |
INTERNAL_SERVER_ERROR("서버 오류입니다. 잠시후 재시도 해주세요", "[ERROR] : 예상치못한 에러 발생", 500), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이부분도 직접 500을 사용하기 보다는 HttpStatus 사용 권장 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다. 응답객체를 만들어주는 부분에서 http 응답코드를 정하면 enum에 정의할 필요가 없다고 생각해요. 어떻게 생각하시나요 |
||
USER_NOT_FOUND("사용자가 존재하지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보를 찾을 수 없음", 404), | ||
INVALID_USER_INFO("사용자 정보가 올바르지 않습니다. 다시 입력해주세요", "[ERROR] : 사용자 정보가 올바르지 않음", 400), | ||
DEACTIVATED_USER("탈퇴한 사용자입니다. 다시 확인해주세요", "[ERROR] : 탈퇴한 사용자", 400); | ||
DEACTIVATED_USER("비활성화된 사용자입니다. 다시 확인해주세요", "[ERROR] : 탈퇴한 사용자", 400), | ||
USER_ALREADY_EXISTS("이미 존재하는 사용자입니다.", "[ERROR] : 이미 존재하는 사용자", 400); | ||
|
||
private final String message; | ||
private final String logMessage; | ||
private final int statusCode; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package gdsc.konkuk.platformcore.application.member; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
import static org.mockito.ArgumentMatchers.*; | ||
import static org.mockito.BDDMockito.*; | ||
|
||
import java.util.Optional; | ||
|
||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.Mock; | ||
import org.mockito.MockitoAnnotations; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException; | ||
import gdsc.konkuk.platformcore.domain.member.entity.MemberRole; | ||
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; | ||
import gdsc.konkuk.platformcore.global.responses.SuccessResponse; | ||
|
||
class MemberServiceTest { | ||
|
||
private MemberService subject; | ||
|
||
@Mock | ||
private MemberRepository memberRepository; | ||
|
||
@Mock | ||
private PasswordEncoder passwordEncoder; | ||
|
||
|
||
@BeforeEach | ||
void setUp() { | ||
MockitoAnnotations.openMocks(this); | ||
subject = new MemberService(passwordEncoder, memberRepository); | ||
} | ||
|
||
@Test | ||
@DisplayName("새로운 멤버 회원가입 성공") | ||
@Transactional | ||
void should_success_when_newMember_register() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 class의 이름은 MemberServiceTest 인데,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로컬에서 테스트할때 레포지토리를 살려놓은상태에서도 진행했는데 모킹하고나서도 그대로 놓은것 같습니다! 이 테스트에서는 서비스 레이어만 테스트하고자하여 삭제하겠습니다 😀 |
||
//given | ||
MemberRegisterRequest memberRegisterRequest = | ||
MemberRegisterRequest.builder() | ||
.memberId("202011288") | ||
.password("password") | ||
.email("[email protected]") | ||
.name("홍길동") | ||
.memberRole(MemberRole.MEMBER) | ||
.batch(2024) | ||
.build(); | ||
given(memberRepository.findByMemberId(any())).willReturn(Optional.empty()); | ||
given(passwordEncoder.encode(any())).willReturn("password"); | ||
//when | ||
SuccessResponse expected = SuccessResponse.messageOnly(); | ||
SuccessResponse actual = subject.register(memberRegisterRequest); | ||
//then | ||
assertEquals(expected.isSuccess(), actual.isSuccess()); | ||
assertEquals(expected.getData(), actual.getData()); | ||
} | ||
|
||
@Test | ||
@DisplayName("이미 존재하는 멤버 회원가입 실패") | ||
@Transactional | ||
void should_fail_when_already_exist_member_register() { | ||
//given | ||
MemberRegisterRequest memberRegisterRequest = | ||
MemberRegisterRequest.builder() | ||
.memberId("202011288") | ||
.password("password") | ||
.email("[email protected]") | ||
.name("홍길동") | ||
.memberRole(MemberRole.MEMBER) | ||
.batch(2024) | ||
.build(); | ||
given(memberRepository.findByMemberId(any())) | ||
.willReturn(Optional.of(MemberRegisterRequest | ||
.toEntity(memberRegisterRequest))); | ||
|
||
//then | ||
assertThrows(UserAlreadyExistException.class, () -> { | ||
//when | ||
subject.register(memberRegisterRequest); | ||
}); | ||
} | ||
} |
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.
builder를 여기다 선언하면 다음과 같은 코드도 생성될것 같습니다.
또한 인자로 null도 전달 가능한 상황입니다. 방어적인 생성이 필요하다 봅니다.