Project :: Conventions

Jay Mild Lee·2023년 2월 14일
0

Project :: Oncounter

목록 보기
3/3

I. 개요

Spring :: Project Structure에서도 언급했지만, 팀 단위 Back-End 개발에 앞서 Convention의 결정은 필수적이다. 개인별로 상이한 개발 경험과 수준을 생각했을 때, 다른 팀원이 작성한 코드를 이해하는 속도는 Convention의 유무에 따라 극명한 차이가 나타난다. 당장 협업을 진행하는 것 뿐만 아니라, 버그 픽스, 기능 확장 등 많은 영역에서 Convention은 주요하게 작용한다.

이에 따라 이번 프로젝트에서 역시 Convention을 적용하기로 하였으며, 적용된 Convention들 중 주요한 사항들을 기록해보고자 한다.

II. Spring Convention

1. DTO(Data Transfer Object)

계층 간 데이터 교환을 하기 위해 사용하는 객체로, 로직을 가지지 않는 순수한 데이터 객체. Controller, Service Layer에서만 사용하며, Repository Layer에서는 사용을 지양한다.

1) Naming

1. Controller Layer

  • [Request or Response] + [Service 이름] + Dto
    ex) RequestSignUpDto

2. Service Layer

  • [Service 이름] + Dto
    ex) SignUpDto

2. Response

1) 공통

  1. Controller 이후 Layer에서 발생하는 모든 Response는 ResponseEntity 를 사용해 반환한다.
  2. ResponseCode의 네 자리 중 끝자리는 도메인 별로 분류한다.
    • Member:0 Post:1 CollaboRequest:2 Music:3 Comment:4 Tag:5 Like:6 SSE:7
    • 401(인증, 토큰) 관련 예외 처리는 Filter에서 진행되므로 해당 규칙에서 제외한다.

2) Success Response

✅ 사용 예시 : return new Response.toResponseEntity(LOGIN_USER_SUCCESS);

1. Flow Diagram

2. SuccessResponse.java

messageStatusCode, CustomHttpStatus, DTO를 HTTP의 Body에 넣고 ResponseEntity를 구성하기 위해 사용한다.

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Getter;
import org.springframework.http.ResponseEntity;

@Getter
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SuccessResponse<T> {
    private final Integer customHttpStatus;
    private final String message;
    private T data;

    public static<T> ResponseEntity<SuccessResponse<T>> toResponseEntity(SucessCode sucessCode, T data){
        return ResponseEntity
                .status(sucessCode.getHttpStatus())
                .body(SuccessResponse.<T>builder()
                        .customHttpStatus(sucessCode.getCustomHttpStatusCode())
                        .message(sucessCode.getMessage())
                        .data(data)
                        .build());
    }
}

3. ResponseCode.java

messageHttpStatusCode를 정의한 Enum. 주로 Controller에서 사용.

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

// Domain이 커지게 될 경우, 해당 객체를 Abstract로 선언 후 Domain별 extend
@Getter
@AllArgsConstructor
public enum SucessCode {
    // Member
    SIGNUP_MEMBER(HttpStatus.OK, "회원 가입 성공", 2000);

    private final HttpStatus httpStatus;
    private final String message;
    private final Integer customHttpStatusCode;
}

3) Exception Response

✅ 사용 예시 : throw new DuplicationException(MEMBER, SERVICE, DUPLICATED_EMAIL, "Email : " + email);

ExceptionResponse의 핵심은 log4j를 통해 Exception에 관련된 Log를 남길 때,
필요한 정보를 얼마나 간결하게 담아내는가?였다. Exception의 원인 파악을 위해서는 1)도메인, 2)계층, 3)ExceptionCode, 4)Exception의 원인이된 Value 총 4가지 정보로 충분할 것이라 판단했고, CustomException에 한해 적용했다.

1. Flow Diagram

2. CustomException.java

프로그래머가 의도적으로 throw 한 Exception을 처리하기 위해 선언

package com.bluehair.hanghaefinalproject.common.exception;

import com.bluehair.hanghaefinalproject.common.response.error.ErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class DuplicationException extends IllegalArgumentException{
    private Domain domain;
    private Layer layer;
    private ErrorCode errorCode;
    private Object causeVariable;
}

3. ErrorCode.java

messageHttpStatusCode를 정의한 Enum. 주로 throw를 통해 Exception을 발생시키고자 할 때 사용한다.

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

// Domain이 커지게 될 경우, 해당 객체를 Abstract로 선언 후 Domain별 extend
@Getter
@AllArgsConstructor
public enum ErrorCode {
    INVALID_EMAIL(HttpStatus.OK, "유효하지 않은 이메일입니다.", 4041);

    private final HttpStatus httpStatus;
    private final String message;
    private final Integer customHttpStatusCode;

}

4. ErrorResponse.java

MessageStatusCode, EnumName을 HTTP의 Body에 담아 ResponseEntity를 구성하기 위해 사용한다.

package com.bluehair.hanghaefinalproject.common.response.error;

import lombok.Builder;
import lombok.Getter;
import org.springframework.http.ResponseEntity;

import java.time.LocalDateTime;

@Getter
@Builder
public class ErrorResponse {
    private final LocalDateTime timestamp = LocalDateTime.now();
    private final Integer customHttpStatus;
    private final String message;

    public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode) {
        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(ErrorResponse.builder()
                        .customHttpStatus(errorCode.getCustomHttpStatusCode())
                        .message(errorCode.getMessage())
                        .build());
    }
}

5. GlobalExceptionHandler.java

ErrorResponse.java , Domain.java, Layer.java에 의해 생성된 ResponseEntity를 Exception이 발생했을 때 반환하기 위해 사용한다.

package com.bluehair.hanghaefinalproject.common.exception;

import com.bluehair.hanghaefinalproject.common.response.error.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException e) {
        log.error("NotFoundException throwed at " + e.getDomain() + "_"+ e.getLayer() + " : " + e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleNotAuthorizedMemberException(NotAuthorizedMemberException e) {
        log.error("NotAuthorizedMemberException throwed at " + e.getDomain() + "_"+ e.getLayer() + " : " + e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleFormatException(FormatException e) {
        log.error("FormatException throwed at " + e.getDomain() + "_"+ e.getLayer() + " : " + e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleDuplicationException(DuplicationException e) {
        log.error("DuplicationException throwed at " + e.getDomain() + "_"+ e.getLayer() + " : " + e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleInvalidRequestException(InvalidRequestException e) {
        log.error("InvalidException throwed at " + e.getDomain() + "_"+ e.getLayer() + " : " + e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }
}

2. Swagger 3.0 Convention

아래는 Swagger 3.0을 도입하면서 팀 노션에 작성했던 글(매뉴얼)이다. 코드에 대한 설명, Swagger 적용 후 사진 등 정보량이 많아 노션 내용을 그대로 공유한다.

Swagger 3.0 Convention

II. Git Convention

한주님이 사용하셨던 컨벤션의 도움을 많이 받았다.

1. Commit

Tag NameDescription
feat새로운 기능 추가
fix버그 수정
designCSS 등 사용자 UI 디자인 변경
!HOTFIX급하게 치명적인 버그를 고쳐야하는 경우
style코드 포맷 변경, 세미 콜론 누락, 코드 수정이 없는 경우
refactor프로덕션 코드 리팩토링
comment필요한 주석 추가 및 변경
docs문서 수정
test테스트 코드
chore빌드 업무 수정, 패키지 매니저 수정
rename파일 혹은 폴더명을 수정하거나 옮기는 작업
remove파일을 삭제하는 작업

커밋 메세지 앞에 Tag Name을 작성한다.

  • ex) feat: 로그인 기능 추가
  • ex) refactor: posting service 내 인증 로직 필터로 분리

2. Issue

  • 작업 전 Issue를 만들고 시작

  • 이슈 템플릿을 활용해 형식에 맞는 Issue 생성

1) Issue Templates

1. Feature

---
name: "✔ Feature"
about: 새로 시작할 작업의 내용과 방향에 대해 작성해주세요.
title: ''
labels: ''
assignees: ''

---

<!--
✔ Feat: 
-->
## I. Description
### 1. 내용

### 2. 방향

## II. Todo
- [ ] 할 일
- [ ] 할 일

## Etc
> 추가 내용(인용 등)

2. Bug

---
name: "\U0001F41E BUG"
about: 발생한 문제와 상황을 작성해주세요
title: ''
labels: ''
assignees: ''

---

<!--
🐞 BUG: 
-->
## Description
버그 내용을 작성하세요.

## 재현 과정
어떤 상황에 버그가 발생하는지 작성하세요.
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

## 예상하는 정상 작동
정상 작동시 어떤 결과가 나와야 하는지 작성해주세요.

## 스크린샷
가능하다면 스크린샷을 첨부해주세요.

## 버그 환경
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

## ETC

3. Refactor

---
name: "\U0001F48E Refactor"
about: 기능 상의 변경이 없는 구조의 변경이나 코드스타일의 변경에 대해 작성해주세요.
title: ''
labels: ''
assignees: ''

---

<!--
💎 Refactor: 
-->
## Description
설명을 입력해주세요.


## Before
변경전의 상황과 변경하려는 이유를 작성해주세요.


## After
변경후의 예상하는 구조를 작성해주세요.


## Todo
- [ ] todo
- [ ] todo

## ETC

3. Pull Request

  • PR 템플릿 형식에 맞게 작성

  • PR 보낸 후 슬랙에 코드 리뷰 요청

  • 리뷰어 지정은 PR 보낸 사람이 알아서 지정

  • PR 머지는 보낸 사람이 머지

  • PR 상황 FE 에게 공유

1) Pull Request Template

## PR 체크사항
PR이 다음 사항을 만족하는지 확인해주세요.

<!-- 
체크하려면 괄호 안에 "x"를 입력하세요. 
각 규칙은 Convention 문서에 있습니다.
PR 제목에 쓰는 prefix는 다음과 같습니다.
🚀 Release
🐞 BugFix
✨ Feat
📝 Doc
💎 Refactor
🔧 Chore
⏪️ Revert
🧪 Test
🎉 Init
-->

- [ ] 커밋 제목 규칙
- [ ] 커밋 메시지 작성 가이드라인
- [ ] 라벨, 담당자, 리뷰어 지정


## PR 타입
어떤 유형의 PR인지 체크해주세요.

<!-- 체크하려면 괄호 안에 "x"를 입력하세요. -->

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Documentation content changes
- [ ] Other... Please describe:


## PR 설명
내용을 적어주세요.


## 작업사항
- 내용을 적어주세요.


## 변경로직
- 내용을 적어주세요.

4. Branch

  • feature/#[이슈번호] → develop
    • 예시 feature/#1
    • feature 브랜치에서 기능을 완성을 한 후 develop 브랜치로 PR
  • refactor/#[이슈번호] → develop
    • 예시 refactor/#10
    • refactor 브랜치에서 refactoring 후 develop 브랜치로 PR
  • 프로젝트 끝난 후 develop → main 머지

0개의 댓글