인스타그램 클론코딩 프로젝트의 backend 부분 github입니다.
1. Explore the Organization
2. Explore Front Repository
Report Bug
·
Request Feature
Backend
통일된 Error Response 객체
{
"message": "Invalid Input Value",
"status": 400,
"errors": [
{
"field": "name.last",
"value": "",
"reason": "must not be empty"
},
{
"field": "name.first",
"value": "",
"reason": "must not be empty"
}
],
"code": "C001"
}
Error Response 객체
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {
private String message;
private int status;
private List<FieldError> errors;
private String code;
...
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class FieldError {
private String field;
private String value;
private String reason;
...
}
}
Error Code 정의
public enum ErrorCode {
// Common
INVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
....
HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
// Member
EMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
;
private final String code;
private final String message;
private int status;
ErrorCode(final int status, final String code, final String message) {
this.status = status;
this.message = message;
this.code = code;
}
}
비즈니스 예외를 위한 최상위 BusinessException 클래스
@Getter
public class BusinessException extends RuntimeException {
private ErrorCode errorCode;
private List<ErrorResponse.FieldError> errors = new ArrayList<>();
public BusinessException(String message, ErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public BusinessException(ErrorCode errorCode, List<ErrorResponse.FieldError> errors) {
super(errorCode.getMessage());
this.errors = errors;
this.errorCode = errorCode;
}
}
@RestControllerAdvice로 모든 예외를 핸들링
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
final ErrorResponse response = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException e) {
final ErrorResponse response = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
final ErrorResponse response = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
final ErrorResponse response = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartException e) {
final ErrorResponse response = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
final ErrorResponse response = ErrorResponse.of(e);
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
final ErrorResponse response = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
final List<ErrorResponse.FieldError> errors = new ArrayList<>();
errors.add(new ErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
final ErrorResponse response = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
final ErrorCode errorCode = e.getErrorCode();
final ErrorResponse response = ErrorResponse.of(errorCode, e.getErrors());
return new ResponseEntity<>(response, BAD_REQUEST);
}
@ExceptionHandler
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
final ErrorResponse response = ErrorResponse.of(INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
통일된 Result Response 객체
{
"status": 200,
"code": "M109",
"message": "회원 이미지 변경에 성공하였습니다.",
"data": {
"status": "success",
"imageUrl": "https://xxx.com/A.jpg"
}
}
Result Respone 객체
@Getter
public class ResultResponse {
private int status;
private String code;
private String message;
private Object data;
public static ResultResponse of(ResultCode resultCode, Object data) {
return new ResultResponse(resultCode, data);
}
public ResultResponse(ResultCode resultCode, Object data) {
this.status = resultCode.getStatus();
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
this.data = data;
}
}
@RestController에서 통일된 응답 사용
@RestController
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
@PostMapping("/posts")
public ResponseEntity<ResultResponse> createPost(@Validated @ModelAttribute PostUploadRequest request) {
...
return ResponseEntity.ok(ResultResponse.of(CREATE_POST_SUCCESS, response));
}
...
}
[Common]
ex) register_date⭕ reg_date❌
ex) register_date⭕ registered_date❌
underscore(_)
로 연결 (snake case)[Table]
_and_
또는 _has_
로 연결
ex)
- 복수형:
articles
,movies
- 약어도 예외 없이 소문자 & underscore 연결:
vip_members
- 교차 테이블 연결:
articles_and_movies
[Column]
테이블 명 단수형_id
으로 사용
ex)
article_id
_flag
접미어 사용_date
접미어 사용[Index]
uix
six
nix
접두어-테이블 명-컬럼 명
ex)
uix-accounts-login_email
[Reference]
└── src
├── main
│ ├── java
│ │ └── cloneproject.instagram
│ │ ├── domain
│ │ │ ├── member
│ │ │ │ ├── controller
│ │ │ │ ├── service
│ │ │ │ ├── repository
│ │ │ │ │ ├── jdbc
│ │ │ │ │ └── querydsl
│ │ │ │ ├── entity
│ │ │ │ ├── dto
│ │ │ │ ├── vo
│ │ │ │ └── exception
│ │ │ ├── feed
│ │ │ │ ├── controller
│ │ │ │ ├── service
│ │ │ │ ├── repository
│ │ │ │ │ ├── jdbc
│ │ │ │ │ └── querydsl
│ │ │ │ ├── entity
│ │ │ │ ├── dto
│ │ │ │ ├── vo
│ │ │ │ └── exception
│ │ │ ├── ...
│ │ ├── global
│ │ │ ├── config
│ │ │ │ ├── SwaggerConfig.java
│ │ │ │ ├── ...
│ │ │ │ └── security
│ │ │ ├── dto
│ │ │ ├── error
│ │ │ │ ├── ErrorResponse.java
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ ├── ErrorCode.java
│ │ │ │ └── exception
│ │ │ │ ├── BusinessException.java
│ │ │ │ ├── EntityNotFoundException.java
│ │ │ │ ├── ...
│ │ │ │ └── InvalidValueException.java
│ │ │ ├── result
│ │ │ │ ├── ResultResponse.java
│ │ │ │ └── ResultCode.java
│ │ │ ├── util
│ │ │ ├── validator
│ │ │ └── vo
│ │ └── infra
│ │ ├── aws
│ │ ├── geoip
│ │ └── email
│ └── resources
│ ├── application-dev.yml
│ ├── application-local.yml
│ ├── application-prod.yml
│ └── application.yml
Type: Subject
ex) Feat: 회원가입 API 추가
Description
Footer
ex) Resolves: #1, #2
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
git checkout -b feature/AmazingFeature
)git commit -m 'Add some AmazingFeature'
)git push origin feature/AmazingFeature
)
seonpilKim 💻 |
bluetifulc 💻 |
JunhuiPark 💻 |
Distributed under the MIT License. See LICENSE.txt
for more information.
SeonPil Kim - ksp970306@gmail.com
Use this space to list resources you find helpful and would like to give credit to. I've included a few of my favorites to kick things off!