Instagram-Clone-Coding / Spring_instagram-clone

Instagram Clone Coding - Backend using Spring Framework
http://ec2-52-79-71-191.ap-northeast-2.compute.amazonaws.com
MIT License
80 stars 23 forks source link
aws backend gradle instagram java jpa junit5 jwt mariadb mockito mysql querydsl redis side-project spring spring-boot spring-security stomp swagger websocket

Contributors Forks Stargazers Issues Pull Requests MIT License


Logo

BE-Instagram-Clone

인스타그램 클론코딩 프로젝트의 backend 부분 github입니다.
1. Explore the Organization
2. Explore Front Repository

Report Bug · Request Feature

Table of Contents
  1. Built With
  2. Getting Started
  3. Contributing
  4. License
  5. Contact
  6. Acknowledgments

Built With

Backend

(back to top)

Getting Started

Convention

  1. 통일된 Error Response 객체

    • Error Response JSON
      {
        "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"
      }
      • message : 에러에 대한 message를 작성합니다.
      • status : http status code를 작성합니다.
      • errors : 요청 값에 대한 field, value, reason 작성합니다. 일반적으로 @Validated 어노테이션으로 Bean Validation에 대한 검증을 진행 합니다.
        • 만약 errors에 binding된 결과가 없을 경우 null이 아니라 빈 배열 []을 응답합니다.
      • code : 에러에 할당되는 유니크한 코드 값입니다.
    • 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;
              ...
          }
      }
  2. 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;
        }
    }
  3. 비즈니스 예외를 위한 최상위 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;
        }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @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);
        }
    }
  5. 통일된 Result Response 객체

    • Result Response JSON
      {
          "status": 200,
          "code": "M109",
          "message": "회원 이미지 변경에 성공하였습니다.",
          "data": {
              "status": "success",
              "imageUrl": "https://xxx.com/A.jpg"
          }
      }
      • message : 결과에 대한 message를 작성합니다.
      • status : http status code를 작성합니다.
      • data : 결과 객체를 JSON 형태로 나타냅니다.
      • code : 결과에 할당되는 유니크한 코드 값입니다.
    • 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;
        }
      }
  6. @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));
        }
        ...
    }

    Java Code Convention

Database Convention

[Common]

[Table]

[Column]

[Index]

[Reference]

Package Structure

└── 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

Commit Convention

Type: Subject
ex) Feat: 회원가입 API 추가

Description

Footer 
ex) Resolves: #1, #2

ERD

erd

Contributing

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!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Contributors


seonpilKim

💻

bluetifulc

💻

JunhuiPark

💻

(back to top)

License

Distributed under the MIT License. See LICENSE.txt for more information.

(back to top)

Contact

SeonPil Kim - ksp970306@gmail.com

(back to top)

Acknowledgments

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!

(back to top)