Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Spring_instagram-clone/README.md at develop · Instagram-Clone-Coding/Spring_instagram-clone · GitHub
Skip to content

Latest commit

History

History
574 lines (489 loc) · 22.9 KB

File metadata and controls

574 lines (489 loc) · 22.9 KB

ContributorsForksStargazersIssuesPull RequestsMIT 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)
      publicclassErrorResponse {
      privateStringmessage;
      privateintstatus;
      privateList<FieldError> errors;
      privateStringcode;
      ...
      @Getter@NoArgsConstructor(access = AccessLevel.PROTECTED)
      publicstaticclassFieldError {
      privateStringfield;
      privateStringvalue;
      privateStringreason;
      ...
      }
      }
  2. Error Code 정의

    publicenumErrorCode {
    // CommonINVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"),
    METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"),
    ....
    HANDLE_ACCESS_DENIED(403, "C006", "Access is Denied"),
    // MemberEMAIL_DUPLICATION(400, "M001", "Email is Duplication"),
    LOGIN_INPUT_INVALID(400, "M002", "Login input is invalid"),
    ;
    privatefinalStringcode;
    privatefinalStringmessage;
    privateintstatus;
    ErrorCode(finalintstatus, finalStringcode, finalStringmessage) {
    this.status = status;
    this.message = message;
    this.code = code;
    }
    }
  3. 비즈니스 예외를 위한 최상위 BusinessException 클래스

    @GetterpublicclassBusinessExceptionextendsRuntimeException {
    privateErrorCodeerrorCode;
    privateList<ErrorResponse.FieldError> errors = newArrayList<>();
    publicBusinessException(Stringmessage, ErrorCodeerrorCode) {
    super(message);
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode) {
    super(errorCode.getMessage());
    this.errorCode = errorCode;
    }
    publicBusinessException(ErrorCodeerrorCode, List<ErrorResponse.FieldError> errors) {
    super(errorCode.getMessage());
    this.errors = errors;
    this.errorCode = errorCode;
    }
    }
    • 모든 비지니스 예외는 BusinessException을 상속 받고, 하나의 BusinessException handler 메소드로 한 번에 처리합니다.
  4. @RestControllerAdvice로 모든 예외를 핸들링

    @RestControllerAdvicepublicclassGlobalExceptionHandler {
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getParameterName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getConstraintViolations());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBindException(BindExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getBindingResult());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMissingServletRequestPartException(MissingServletRequestPartExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INPUT_VALUE_INVALID, e.getRequestPartName());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(e);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpMessageNotReadableException(HttpMessageNotReadableExceptione) {
    finalErrorResponseresponse = ErrorResponse.of(HTTP_MESSAGE_NOT_READABLE);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedExceptione) {
    finalList<ErrorResponse.FieldError> errors = newArrayList<>();
    errors.add(newErrorResponse.FieldError("http method", e.getMethod(), METHOD_NOT_ALLOWED.getMessage()));
    finalErrorResponseresponse = ErrorResponse.of(HTTP_HEADER_INVALID, errors);
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleBusinessException(BusinessExceptione) {
    finalErrorCodeerrorCode = e.getErrorCode();
    finalErrorResponseresponse = ErrorResponse.of(errorCode, e.getErrors());
    returnnewResponseEntity<>(response, BAD_REQUEST);
    }
    @ExceptionHandlerprotectedResponseEntity<ErrorResponse> handleException(Exceptione) {
    finalErrorResponseresponse = ErrorResponse.of(INTERNAL_SERVER_ERROR);
    returnnewResponseEntity<>(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 객체
      @GetterpublicclassResultResponse {
      privateintstatus;
      privateStringcode;
      privateStringmessage;
      privateObjectdata;
      publicstaticResultResponseof(ResultCoderesultCode, Objectdata) {
      returnnewResultResponse(resultCode, data);
      }
      publicResultResponse(ResultCoderesultCode, Objectdata) {
      this.status = resultCode.getStatus();
      this.code = resultCode.getCode();
      this.message = resultCode.getMessage();
      this.data = data;
      }
      }
  6. @RestController에서 통일된 응답 사용

    @RestController@RequiredArgsConstructorpublicclassPostController {
    privatefinalPostServicepostService;
    @ApiOperation(value = "게시물 업로드", consumes = MULTIPART_FORM_DATA_VALUE)
    @PostMapping("/posts")
    publicResponseEntity<ResultResponse> createPost(@Validated@ModelAttributePostUploadRequestrequest) {
    ...
    returnResponseEntity.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 - 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)