Skip to content

Instagram-Clone-Coding/Spring_instagram-clone

Repository files navigation

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]

  • 소문자 사용
  • 단어 임의로 축약 x

    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]

  • PK는 테이블 명 단수형_id으로 사용

    ex) article_id

  • FK는 부모 테이블의 PK 이름을 그대로 사용
    • self 참조인 경우, PK 이름 앞에 적절한 접두어 사용
  • boolean 유형의 컬럼은 _flag 접미어 사용
  • date, datetime 유형의 컬럼은 _date 접미어 사용

[Index]

  • 접두어
    1. unique index: uix
    2. spatial index: six
    3. index: nix
  • 접두어-테이블 명-컬럼 명

    ex) uix-accounts-login_email

[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
  • Type
    • Feat: 기능 추가, 삭제, 변경
    • Fix: 버그 수정
    • Refactor: 코드 리팩토링
    • Style: 코드 형식, 정렬 등의 변경. 동작에 영향 x
    • Test: 테스트 코드 추가, 삭제 변경
    • Docs: 문서 추가 삭제 변경. 코드 수정 x
    • Etc: 위에 해당하지 않는 모든 변경
  • Description
    • 한 줄당 72자 이내로 작성
    • 최대한 상세히 작성(why - what)
  • Footer
    • Resolve(s): Issue 해결 시 사용
    • See Also: 참고할 Issue 있을 시 사용
  • Rules
    • 관련된 코드끼리 나누어 Commit
    • 불필요한 Commit 지양
    • 제목은 명령조로 작성
  • Reference

(back to top)

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 - [email protected]

(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)