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

[4기 - 김민희] SpringBoot Part3 WeeklyMission 제출합니다. #867

Open
wants to merge 27 commits into
base: KimMinheee/week01
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c3ede23
[Feat] : build.gradle 의존성 추가
KimMinheee Jul 27, 2023
58b7672
[Feat] : @Service 어노테이션으로 변경
KimMinheee Jul 27, 2023
ac57590
[Feat] : 기존 에러메시지 -> ErrorCode로 수정
KimMinheee Jul 27, 2023
3fc14eb
[Feat] : 요청 성공시 사용하는 BaseResponse 구현
KimMinheee Jul 27, 2023
0994f2b
[Feat] : ErrorResponse 및 ExceptionHandler 구현
KimMinheee Jul 27, 2023
f372e31
[Feat] : 요청 성공시 내려주는 SuccessCode 구현
KimMinheee Jul 27, 2023
ae7edeb
[Feat] : VoucherRestController 구현
KimMinheee Jul 27, 2023
4ba6d1c
[Feat] : controller - service 사이 mapper 구현 및 dto 분리
KimMinheee Jul 27, 2023
1d463a8
[Feat] : DiscountValue 생성자 내에서 유효성 검사하도록 수정
KimMinheee Jul 27, 2023
48125fd
[Feat] : ErrorCode로의 수정으로 인한 import문 변경
KimMinheee Jul 27, 2023
3cd95ee
[Feat] : Voucher 도메인 layer 별로 dto 구분
KimMinheee Jul 27, 2023
845c28c
[Feat] : 에러코드(10002) 메시지 수정
KimMinheee Jul 27, 2023
b21a1c3
[Feat] : 기존 ExceptionMessage enum 파일 삭제
KimMinheee Jul 27, 2023
7d85b6c
[Feat] : 기존 Member 도메인 id 변수명 수정
KimMinheee Jul 28, 2023
7f990fe
[Feat] : Member RestAPI 개발 및 계층 별 dto 사용하도록 수정
KimMinheee Jul 28, 2023
6f1e443
[Feat] : VoucherMapper 내 메소드명 수정
KimMinheee Jul 28, 2023
503e7d7
[Feat] : Wallet RestApi 개발 및 계층 별 dto 구현
KimMinheee Jul 28, 2023
a250699
[Refactor] : sql 쿼리 문 내 키워드 대문자로 수정
KimMinheee Jul 28, 2023
f4fdb1f
[Refactor] : BaseTimeEntity 구현
KimMinheee Jul 28, 2023
98797c9
[Feat] : Ulid 의존성 추가
KimMinheee Jul 28, 2023
09b519e
[Feat] : Member 객체 id Ulid로 변경
KimMinheee Jul 28, 2023
bb5fd7e
[Feat] : Voucher 객체 id Ulid로 변경
KimMinheee Jul 28, 2023
5c5bd26
[Feat] : Wallet 객체 id Ulid로 변경
KimMinheee Jul 28, 2023
8597271
[Feat] : 테스트 db sql문 수정
KimMinheee Jul 29, 2023
7d99b66
[Refactor] : DiscountValue 생성시 validate 확인 메소드 수정
KimMinheee Jul 29, 2023
c9777fe
[Feat] : Wallet,Member,Voucher Exception 생성자 내 RuntimeException 생성자 호…
KimMinheee Jul 29, 2023
c1d69a3
[Feat] : 코드 변경에 따른 테스트 코드 수정
KimMinheee Jul 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ dependencies {

//configuration
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

//web
implementation 'org.springframework.boot:spring-boot-starter-web'

//map struct
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

//validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// ulid-creator
implementation group: 'com.github.f4b6a3', name: 'ulid-creator', version: '5.1.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.programmers.VoucherManagement.global.entity;

import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
Copy link

Choose a reason for hiding this comment

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

이 entity는 Component가 아닌것 같아요

public class BaseTimeEntity {
protected String createdAt;

public BaseTimeEntity() {
this.createdAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}

public String getCreatedAt() {
return createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.programmers.VoucherManagement.global.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.programmers.VoucherManagement.global.response.ErrorCode;
import org.programmers.VoucherManagement.global.response.ErrorResponse;
import org.programmers.VoucherManagement.member.exception.MemberException;
import org.programmers.VoucherManagement.voucher.exception.VoucherException;
import org.programmers.VoucherManagement.wallet.exception.WalletException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@RequiredArgsConstructor
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
Copy link

Choose a reason for hiding this comment

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

이름좋은데요!
Rest Controller에 관련된 예외를 처리하는 handler다 라고 조금 더 명확하게 주면 어떨까요?

/**
* [Exception] 객체 혹은 파라미터의 데이터 값이 유효하지 않은 경우
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("Handle MothodArgumentNotValidException", e.getMessage());
Copy link

Choose a reason for hiding this comment

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

TRACE, DEBUG, INFO, WARN, ERROR 등
로그의 레벨이 나뉘어져 있어요.
각각 나뉜 의도가 있습니다.
MethodArgumentNotValidExceptionr가 발생하면 error일까요?
그렇다면 이 로그는 error일까요?

final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_METHOD_ERROR, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
Copy link

Choose a reason for hiding this comment

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

ResponseEntity.badRequest().body() 라는 정적 팩토리 메소드가 존재합니다.

}

/**
* [Exception] 클라이언트에서 request로 '파라미터로' 데이터가 넘어오지 않았을 경우
*
* @param ex MissingServletRequestParameterException
* @return ResponseEntity<ErrorResponse>
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
protected ResponseEntity<ErrorResponse> handleMissingRequestHeaderExceptionException(
MissingServletRequestParameterException ex) {
log.error("Handle MissingServletRequestParameterException", ex);
final ErrorResponse response = ErrorResponse.of(ErrorCode.REQUEST_PARAM_MISSING_ERROR, ex.getMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

/**
* [Exception] enum type 일치하지 않아 binding 못할 경우
* 주로 @RequestParam enum으로 binding 못했을 경우 발생
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error("Handle MethodArgumentTypeMismatchException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE_ERROR, e.getMessage());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}


/**
* [Exception] com.fasterxml.jackson.core 내에 Exception 발생하는 경우
*
* @param ex JsonProcessingException
* @return ResponseEntity<ErrorResponse>
*/
@ExceptionHandler(JsonProcessingException.class)
protected ResponseEntity<ErrorResponse> handleJsonProcessingException(JsonProcessingException ex) {
log.error("handleJsonProcessingException", ex);
final ErrorResponse response = ErrorResponse.of(ErrorCode.REQUEST_BODY_MISSING_ERROR, ex.getMessage());
return new ResponseEntity<>(response, HttpStatus.OK);
Copy link

Choose a reason for hiding this comment

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

ResponseEntity.ok()

}

/**
* [Exception] @ModelAttribute 으로 binding error 발생할 경우
*/
@ExceptionHandler(BindException.class)
protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
log.error("Handle BindException : ", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE_ERROR, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

/**
* [Exception] 서버에 정의되지 않은 모든 예외
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllException(Exception e) {
log.error("Handle Exception :", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
return new ResponseEntity<>(response, HttpStatus.OK);
Comment on lines +89 to +90

Choose a reason for hiding this comment

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

Exception은 internal server error가 맞는 것 같아요.

}

/**
* [Exception] 커스텀 예외 - MemberException
*/
@ExceptionHandler(MemberException.class)
public ResponseEntity<ErrorResponse> handleNotFoundException(MemberException e) {
log.error("Handle NotFoundException :", e);
final ErrorResponse response = ErrorResponse.of(e.getErrorCode(), e.getMessage());
return new ResponseEntity<>(response, HttpStatus.OK);
}
Comment on lines +96 to +101

Choose a reason for hiding this comment

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

404 혹은 400이 맞는 에러코드인 것 같습니다. 멤버를 못찾는 요청이 잘못된 경우(400), 찾는 멤버가 없는 경우(404)


/**
* [Exception] 커스텀 예외 - VoucherException
*/
@ExceptionHandler(VoucherException.class)
public ResponseEntity<ErrorResponse> handlePermissionDeniedException(VoucherException e) {
log.error("Handle PermissionDeniedException :", e);
final ErrorResponse response = ErrorResponse.of(e.getErrorCode(), e.getMessage());
return new ResponseEntity<>(response, HttpStatus.OK);
}
Comment on lines +106 to +111

Choose a reason for hiding this comment

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

permission denied에 맞는 http status 를 알아보시면 좋을 것 같네요. 아래도 마찬가지고요.
인증과 인가 두 개념에 대해서 http status 값에 맞는 경우를 탐구해보면 좋을 것 같습니다.

Choose a reason for hiding this comment

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

혹은 단순히 400으로 볼 수도 있고요. 순전히 요청이 잘못됐다고 서버가 판단해야하는 포괄적인 경우 http status 에 의해서 비즈니스가 노출될 수 있으니깐요.


/**
* [Exception] 커스텀 예외 - WalletException
*/
@ExceptionHandler(WalletException.class)
public ResponseEntity<ErrorResponse> handlePermissionDeniedException(WalletException e) {
log.error("Handle PermissionDeniedException :", e);
final ErrorResponse response = ErrorResponse.of(e.getErrorCode(), e.getMessage());
return new ResponseEntity<>(response, HttpStatus.OK);
}
Comment on lines +116 to +121

Choose a reason for hiding this comment

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

permission denied에 맞는 http status 를 알아보시면 좋을 것 같네요. 아래도 마찬가지고요.
인증과 인가 두 개념에 대해서 http status 값에 맞는 경우를 탐구해보면 좋을 것 같습니다.

Choose a reason for hiding this comment

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

혹은 단순히 400으로 볼 수도 있고요. 순전히 요청이 잘못됐다고 서버가 판단해야하는 포괄적인 경우 http status 에 의해서 비즈니스가 노출될 수 있으니깐요.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.programmers.VoucherManagement.global.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Getter;

import static org.programmers.VoucherManagement.global.response.SuccessCode.SUCCESS;

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"code", "message", "result"})
public class BaseResponse<T> {
private final String message;
private final String code;
@JsonInclude(JsonInclude.Include.NON_NULL)
private T result;

Comment on lines +16 to +18
Copy link

Choose a reason for hiding this comment

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

이부분은 클라이언트와의 협업간에도 고민해볼 포인트 인것같아요.
null로 주면 null처리할 수 있지만, empty인 경우 property가 없다면 당황할것같네요

/**
* 요청에 성공하고 반환값이 있는 경우
*
* @param result
*/
public BaseResponse(T result) {
this.message = SUCCESS.getMessage();
this.code = SUCCESS.getCode();
this.result = result;
}

/**
* 요청에 성공하고 반환값이 없는 경우
*
* @param status
*/
public BaseResponse(SuccessCode status) {
this.message = status.getMessage();
this.code = status.getCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.programmers.VoucherManagement.global.response;

public enum ErrorCode {
Copy link

Choose a reason for hiding this comment

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

LGTM

Http와 관련되었으니 HttpErrorCode는 어떨까요?

/**
* 10000번 -> Global에서 발생하는 에러코드 관리
* [Http Status code]
* 400 : Bad Request
* 401 : Unauthorized
* 403 : Forbidden
* 404 : Not Found
* 405 : Method Not Allowed
* 500 : Internal Server Error
*/
FAIL(500, "10000", "요청에 실패하였습니다."),
INVALID_INPUT_VALUE_ERROR(400, "10001", "유효하지 않은 입력값입니다."),
INVALID_METHOD_ERROR(405, "10002", "Method Argument가 적절하지 않습니다."),
REQUEST_BODY_MISSING_ERROR(400, "10003", "RequestBody에 데이터가 존재하지 않습니다."),
REQUEST_PARAM_MISSING_ERROR(400, "10004", "RequestParam에 데이터가 전달되지 않았습니다."),
INVALID_TYPE_VALUE_ERROR(400, "10005", "타입이 유효하지 않습니다."),
INTERNAL_SERVER_ERROR(500, "10006", "서버 오류 입니다."),

/**
* ==========================================================================
* 20000번 -> Member에서 발생하는 에러코드 관리
* ==========================================================================
*/
NOT_EXIST_MEMBER_STATUS(400, "M20000", "해당하는 회원 상태가 존재하지 않습니다."),
NOT_FOUND_MEMBER(404, "M20001", "회원을 찾을 수 없습니다."),
FAIL_TO_INSERT_MEMBER(500, "M20002", "데이터가 정상적으로 저장되지 않았습니다."),
FAIL_TO_UPDATE_MEMBER(500, "M20003", "데이터가 정상적으로 수정되지 않았습니다."),
FAIL_TO_DELETE_MEMBER(500, "M20004", "데이터가 정상적으로 삭제되지 않았습니다."),

/**
* ==========================================================================
* 30000번 -> Voucher에서 발생하는 에러코드 관리
* ==========================================================================
*/
NOT_INCLUDE_1_TO_100(400, "V30000", "할인율은 1부터 100사이의 값이여야 합니다."),
AMOUNT_IS_NOT_NUMBER(400, "V30001", "숫자만 입력가능합니다."),
NOT_EXIST_COMMAND(400, "V30002", "해당하는 Command가 존재하지 않습니다."),
NOT_EXIST_DISCOUNT_TYPE(400, "V30003", "해당하는 유형의 바우처가 존재하지 않습니다."),
NOT_FOUND_VOUCHER(404, "V30004", "바우처를 찾을 수 없습니다."),
FAIL_TO_INSERT_VOUCHER(500, "V30005", "데이터가 정상적으로 저장되지 않았습니다."),
FAIL_TO_UPDATE_VOUCHER(500, "V30006", "데이터가 정상적으로 수정되지 않았습니다."),
FAIL_TO_DELETE_VOUCHER(500, "V30007", "데이터가 정상적으로 삭제되지 않았습니다."),

/**
* ==========================================================================
* 40000번 -> Wallet에서 발생하는 에러코드 관리
* ==========================================================================
*/
FAIL_TO_INSERT_WALLET(500, "W40000", "데이터가 정상적으로 저장되지 않았습니다."),
FAIL_TO_DELETE_WALLET(500, "W40001", "데이터가 정상적으로 삭제되지 않았습니다.");

Comment on lines +42 to +54
Copy link

Choose a reason for hiding this comment

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

클라이언트에게 500 응답을 줘서 서버에 문제가 있다 라는것을 노출 시킬지 여부를 고민 해보시면 좋을것같아요


private final int status; //코드 상태(Http)
private final String divisionCode; //서버 내 코드 구분 값
private final String message; //에러 코드 메시지

ErrorCode(int status, String divisionCode, String message) {
this.status = status;
this.divisionCode = divisionCode;
this.message = message;
}

public String getMessage() {
return message;
}

public int getStatus() {
return status;
}

public String getDivisionCode() {
return divisionCode;
}

}
Loading