-
Notifications
You must be signed in to change notification settings - Fork 4
백엔드 컨벤션
권예진 edited this page Oct 18, 2023
·
3 revisions
- 솔로 프로그래밍
- 기능이 간단한 경우
- 페어 프로그래밍
- 기능이 너무 복잡하거나 어려운 경우
- 해당 기능을 구현하고 싶은 크루가 다수인 경우
- 솔로 프로그래밍
- 해당 PR을 요청하지 않은 3명의 크루가 모두 리뷰
- 페어 프로그래밍
- 다른 모든 크루들이 리뷰
- 모든 리뷰어가 approve를 해줘야 merge 가능
- merge는 다같이! (캠퍼스)
- 자바 : 17
- 스프링 : 3.0 이상
- 스프링 설정 파일로는 yml을 사용한다. (ex.
application.yml
) - 깃 서브모듈로 설정 파일을 관리한다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Google Java Style Guide를 원칙으로 한다.
- 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다.
- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다.
- 3항 연산자를 쓰지 않는다.
- else 예약어를 쓰지 않는다.
- switch/case도 쓰지 않는다.
- 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다.
- 배열 대신 컬렉션을 사용한다.
- 줄여 쓰지 않는다(축약 금지).
- 일급 컬렉션을 쓴다.
- 모든 엔티티를 작게 유지한다.
-
클래스 이름과 필드 사이에 공백을 둔다.
public class Line { private final Long id; private final LineName name; private final LineColor color; private final Sections sections; }
-
메서드의 매개변수가 많거나 길어서 120자를 넘어가면 개행한다.
public ReadAuctionsDto readAllByCondition( final Pageable pageable, final ReadAuctionSearchCondition readAuctionSearchCondition ) { .... }
- 메소드 파라미터
- 클래스
- 필드
- 변수
- 패키지 외부에서 생성자 및 메소드를 호출해서는 안 되는 경우, 생성자의 접근제어자는
default
로 엄격하게 지킨다.
- 호출 순서에 따라 정렬한다.
- private 메서드를 사용하는 다른 메서드가 많을 경우에도 첫 번째로 사용하는 메서드 바로 밑에 둔다.
public a() { c(); } private c() { } public b() { c(); d(); } private d() { }
- 필드 관련 메서드 외 메서드 명에
get
키워드를 사용하지 않는다. -
process
,calculate
,find
등의 단어로 대체한다.
- 롬복 사용
- 상태를 갖는 객체(도메인 엔티티 등)에서는 일괄적으로 재정의
- 엔티티는 id만, VO는 모든 필드 포함
- 롬복 사용
- 상태를 갖는 객체(도메인 엔티티 등)에서는 일괄적으로 재정의
- 연관관계 매핑 되어있는 필드는 제외
- 롬복 사용
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@EqualsAndHashCode(of = "id", callSuper = false)
@ToString(of = {"id", "..."})
@Table(name = "orders")
- Controller → Service
- 별도의 DTO로 변환
- 클라이언트에게 입력받은 데이터를 유효한 타입으로 변환하는 등의 비즈니스 로직과 연관이 없는 작업 수행
- 별도의 DTO로 변환
- Service → Repository
- 도메인 엔티티(JPA Entity)
- Repository → Service
- 도메인 엔티티(JPA Entity)
- Service → Controller
- create() 의 경우
- Long을 반환
- 특수한 케이스(create의 응답으로 id 이외의 데이터도 넘겨야 하는 경우)에만 별도의 dto를 반환
- 그 외의 경우
- 별도의 DTO로 변환
- create() 의 경우
- Controller에서 반환하는 Response
- 서비스에서 반환하는 dto와 필드 값이 같은 경우에도 API 별로 별도의 Response를 정의
- Request, Response
- (CRUD)Request (ex. CreateAuctionRequest)
- (CRUD)Response (ex. ReadAuctionResponse)
- C : Create
- R : Read
- U : Update
- D : Delete
- 나머지 DTO
- (기능)Dto (ex. ReadAuctionDto)
- DTO에 정적 팩토리 메소드 추가
- 커스텀 예외 사용
- 예외 메시지가 중복되더라도 상수로 빼지 않는다.
-
도메인형 구조
- configuration
- 최상단에 생성
- 도메인별
configuration
생성해야 한다면 패키지 생성
- configuration
-
예시 구조
- line - application (service) - domain - presentation (controller) - infrastructure - jpa - redis - jgrapht - station - application (service) - domain - presentation (controller) - infrastructure (repository)
-
도메인 패키지에 레포지토리 인터페이스 생성
-
infrastructure.persistence 패키지에 레포지토리 구현체 생성 및 구현체가 필요한 repository를 가지고 있는 구조
- domain - Auction.java - AuctionRepository.java (인터페이스) - infrastructure - persistence - AuctionRepositoryImpl.java (인터페이스 구현체) - JpaAuctionRepository.java - QuerydslAuctionRepository.java
public interface AuctionRepository { Auction save(final Auction auction); List<Auction> findAll(); } public class AuctionRepositoryImpl implementation AuctionRepository { private final JpaAuctionRepository jpaAuctionRepository; private final QueryAuctionRepository queryAuctionRepository; @Overide public Auction save(final Auction auction) { return jpaAuctionRepository.save(auction); } @Overide public List<Auction> findById(final Long id) { return querydslAuctionRepository.findAll(id); } } public interface JpaAuctionRepository extends JpaRepository<Auction, Long> { } public class QueryUserRepository { private final QueryFactory queryFactory; List<Auction> findAll() { queryFactory.selectFrom(auction) .leftJoin(~).fetchJoin() .join(~).fetchJoin() .where(auction.id) .orderBy(~) .fetch(); } }
- 테스트 메소드는 한글로 작성한다.
- 한글 사용 시 인텔리제이의 경고를 억제하기 위해
@SuppressWarnings("NonAsciiCharacters")
를 추가한다. - 언더바(_) 대신 공백으로 바꿔주기 위해
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
를 추가한다.
- 한글 사용 시 인텔리제이의 경고를 억제하기 위해
- 도메인
- 단위 테스트
- Repository
-
@DataJpaTest
사용
-
- Service
- 통합테스트
-
@SpringBootTest
사용
- 컨트롤러
- 단위테스트
-
@WebMvcTest
사용 - Mock으로는
BddMockito
사용
- Given - When - Then 주석으로 어떤 절인지 표현한다.
- 테스트 비교 대상 (when절에서 반환하는 객체)의 변수명은
actual
을 사용한다.
Assertions
사용
assertThat().isEqualTo()
assertThatThrownBy()
- 테스트 검증 사항이 2개 이상이라면
SoftAssertions.assertSoftly()
사용
-
@Autowired
- 필드 주입
-
공통
- 변수명 네이밍
- 여러개의 동일 타입의 변수를 생성해야 하는 경우 숫자로 구분한다.
- 클래스 네이밍
- 테스트 클래스 명(-Test) + Fixture
- 필드 네이밍
- 한글로 작성 (픽스처임을 알리고 어떤 의미인지 명확하게 하기 위해)
- 픽스처 클래스에서 픽스처 필드의 접근지정자는 protected, 픽스처가 아닌 필드의 접근 지정자는 private
- 모든 필드는 final을 붙이지 않는다
- 모킹을 한 필드의 필드명에 "mock" 키워드 사용하지 않기
- 해당 테스트가 존재하는 패키지에 픽스처 패키지 생성해서 픽스처 클래스 두기
- auction - fixture - AuctionServiceFixture.java - AuctionServiceTest.java
- 변수명 네이밍
-
통합 테스트
- 각 테스트 별로 클래스로 분리
- 파라미터 DTO와 응답 DTO을 모두 Fixture로 생성
- then절에서 Fixture로 만든 응답 DTO를 결과(actual)과 isEqualTo()로 검증
-
단위 테스트
- 컨트롤러 슬라이스 테스트
- 각 테스트 별로 클래스로 분리
- 픽스처가 적은 테스트라고 할지라도 별도의 클래스에서 픽스처 관리
- given절의 모든 객체를 픽스처로 처리
- mocking 과정(BDDMockito.given() 등등)은 given절에 유지
- mocking 과정 중 발생하는 예외도 given절에 유지
- 예외 발생 시 willThrow() 등의 메서드 내부에서 new 키워드로 생성
- 예외 객체는 별도의 필드로 분리하지 않음
- 예외 메세지 테스트 검증 시 exists() 사용
- 추후 예외 메세지 문서화 시에는 메세지 명시
- 테스트 코드 중 문서화는 별도의 private 메서드로 분리하고 맨 밑으로 메서드 정렬 (ex.
private void createAuction_문서화(final ResultActions resultActions)
)
- 컨트롤러 슬라이스 테스트
-
레포지토리 테스트
- 각 테스트 별로 클래스로 분리
- 픽스처가 적은 테스트라고 할지라도 별도의 클래스에서 픽스처 관리
- 저장 테스트 시
- save()의 id가 positive인지만 확인
-
assertThat(actual.getId()).isPositive()
)
- 조회 테스트 시
- 단일조회 시 Optional 반환값과 픽스쳐를 contains()로 비교
assertThat(actual).contains(expected);
- 목록조회 시 컬렉션의 크기 비교 + 인덱스끼리의 isEqualTo()로 비교
SoftAssertions.assertSoftly(softAssertions -> { softAssertions.assertThat(actual).hasSize(3); softAssertions.assertThat(actual.getTitle()).isEqualTo(~); });
- 각 테스트 별로 클래스로 분리
-
도메인 테스트
- repository 사용하지 않음
- id가 필요한 경우
ReflectionTestUtils.setField()
사용해서 id 세팅 - 테스트하려는 도메인 객체 외에는 모두 픽스처 생성
- 기본적으로는 도메인 엔티티와 동일한 이름을 사용하되, 테이블 명이 예약어와 중복될 경우 복수형을 사용한다.
fk_엔티티이름_필드이름
- 예시:
fk_auction_seller
-
is_deleted
필드를 두고 soft delete 방식 사용
- DB 조건도 JPA 엔티티에도 표현을 한다.
@Entity public class Member { @Id @GeneratedValue @Column(name = "member_id") private Long id; @Column(unique = true, length = 10) // 테이블 제약조건 표현 private String name; }
- spring rest docs 사용
- 컨트롤러 단위테스트를 대상으로 문서화
- andDo 부분은 별도의 메서드로 분리
- 해당 클래스 가장 하단의 메서드를 만들어 andDo를 분리한다.
- 메서드명 : 문서화_{문서화할 컨트롤러 메서드} (ex.
create_문서화
)