인스타그램 클론코딩 프로젝트의 backend 부분 github입니다.
    
    1. Explore the Organization
    2. Explore Front Repository
    
    
    
    
    Report Bug
    ·
    Request Feature
  
Table of Contents
Backend
- Spring Boot
- Spring Security
- Spring Data JPA
- Spring Data Redis
- Spring WebSocket
- Springfox Swagger UI
- JSON Web Token
- Querydsl
- MySQL
- Amazon Web Services
- 
통일된 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; ... } } 
 
- Error Response JSON
- 
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; } } - 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
 
- 
@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 객체 - 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; } } 
 
- Result Response JSON
- 
@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]
- 소문자 사용
- 단어 임의로 축약 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]
- 접두어
- unique index: uix
- spatial index: six
- index: nix
 
- unique index: 
- 접두어-테이블 명-컬럼 명- 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.ymlType: 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
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!
- Fork the Project
- Create your Feature Branch (git checkout -b feature/AmazingFeature)
- Commit your Changes (git commit -m 'Add some AmazingFeature')
- Push to the Branch (git push origin feature/AmazingFeature)
- Open a Pull Request
| seonpilKim 💻 | bluetifulc 💻 | JunhuiPark 💻 | 
Distributed under the MIT License. See LICENSE.txt for more information.
SeonPil Kim - [email protected]
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!
