패키지 가이드
dictionary-api(단어사전 관리 도구 백엔드) 프로젝트의 실제 패키지 구조를 예시로 삼는다.
기본 패키지 패턴
Section titled “기본 패키지 패턴”- 패키지 기본 구성
{도메인}.{프로젝트명}
- 샘플
biz.mintchoco.dictionary
- 도메인(
biz.mintchoco)은 도메인 주소를 역방향으로 표기한다. - 프로젝트명(
dictionary)은 소문자 알파벳과 언더바(_)만 사용한다.
- 도메인(
최상위 패키지 구조
Section titled “최상위 패키지 구조”Directorybiz.mintchoco.dictionary/
- DictionaryApiApplication.java
Directoryconfig/ 전역 설정 빈
- …
Directoryentity/ JPA 엔티티 (기능 하위가 아니라 최상위에 모은다)
- …
Directoryinterfaces/ 외부 API 연동 클라이언트 (여러 기능이 함께 참조하는 공용 레이어)
- …
Directoryapi/ 기능별 레이어드 구조
Directoryboard/
Directorycontroller/
- …
Directorydto/
- …
Directoryrepository/
- …
Directoryservice/
- …
Directorydictionary/
- …
Directorydomain/
- …
- …
Directorycommon/ 프로젝트 공통 부품
Directoryapi/ 컨트롤러 공통 헬퍼, 응답 어드바이스
- …
Directorycode/ 결과 코드·포맷 검증용 enum
- …
Directorydto/ 요청/응답 공통 봉투(envelope)
- …
Directoryexception/ 공통 예외, 전역 예외 처리기
- …
Directorymodel/ 세션 등 도메인에 속하지 않는 공용 모델
- …
Directorysecurity/ 인가 관련 애노테이션·인터셉터
- …
Directoryutils/ 정적 유틸리티
- …
entity,interfaces는 특정 기능(api/<feature>) 하위가 아니라 최상위에 둔다 — 여러 기능이 함께 참조하도록 의도적으로 공유하는 레이어이기 때문이다.entity: JPA 엔티티.AbstractEntity(@MappedSuperclass)를 상속해 등록/수정 감사 컬럼(create_dt/create_user_id/create_ip/update_dt/update_user_id/update_ip)을 자동으로 채운다.interfaces: 외부 API 연동 클라이언트(StdictClient,KrdictClient등).interface가 Java 예약어라 복수형으로 이름 지었다.
api/<feature>: 기능별 레이어드 구조. 기능 하나(board,dictionary,domain,auth등)마다controller/dto/repository/service네 서브패키지를 갖는다.- DB를 거치지 않는 기능(
search,tools)은repository가 없고,RequestDto/ResponseDto사이에Entity를 대신할 중간 표현이 필요하면dto/context서브패키지에Context클래스를 따로 둔다.
- DB를 거치지 않는 기능(
common: 여러 기능이 함께 쓰는 부품만 모은다 — 특정 기능 전용 로직은 여기 두지 않고 해당api/<feature>안에 둔다.
기능(api/<feature>) 서브패키지 가이드
Section titled “기능(api/<feature>) 서브패키지 가이드”- controller
biz.mintchoco.dictionary.api.board.controller
AbstractController를 상속하고@RestController+@RequestMapping("/v1/<feature>")+@RequiredArgsConstructor를 붙인다.
- dto
biz.mintchoco.dictionary.api.board.dto
- API(컨트롤러 메소드) 단위로 독립 파일을 둔다 —
BoardCreate.java,BoardUpdate.java,BoardFindAll.java,BoardFindByPage.java,BoardDeleteAll.java처럼 파일명 자체가<Feature><메소드>. - 각 파일은 같은 이름의 네임스페이스 클래스 안에
<Feature><메소드>RequestDto중첩 클래스를 둔다(BoardCreate.BoardCreateRequestDto). - 응답 DTO(
BoardResponseDto)는 파일을 나누지 않고 한 기능이 API 여러 개(find/findAll/findByPage/create/update)에서 공유한다. - 중간 표현이 필요한 기능은
dto/context서브패키지에Context클래스를 별도 파일로 둔다(예:ToolsJasyptEncryptContext).
- API(컨트롤러 메소드) 단위로 독립 파일을 둔다 —
- repository
biz.mintchoco.dictionary.api.board.repository
- Spring Data JPA 인터페이스(
BoardRepository extends JpaRepository<BoardEntity, Long>)와, 동적 조건·페이징을 담당하는 QueryDsl 커스텀 리포지토리(BoardQueryDslRepository/BoardQueryDslRepositoryImpl)를 함께 둔다.
- Spring Data JPA 인터페이스(
- service
biz.mintchoco.dictionary.api.board.service
@Service+@RequiredArgsConstructor. 리포지토리 하나만 생성자 주입 필드로 둔다.
DTO 클래스 네이밍(<Feature> 접두어)
Section titled “DTO 클래스 네이밍(<Feature> 접두어)”- 모든 DTO 클래스명 맨 앞에 기능 이름을 접두어로 붙인다 —
BoardCreate,DomainFindAll,ToolsJasyptEncrypt. 중첩 클래스도 줄여 쓰지 않고 전체 이름을 반복한다(ToolsJasyptEncrypt.ToolsJasyptEncryptRequestDto,ToolsJasyptEncrypt.RequestDto처럼 줄이지 않는다). - 이유
- 프로젝트 전체에서 DTO 클래스명이 유일해져, 서로 다른 기능의 DTO를 한 파일에서 같이 import해도 이름이 겹치지 않는다.
- springdoc이 생성하는
/v3/api-docs스키마 이름은 Java 클래스의 simple name을 그대로 쓴다 — 접두어가 없으면 여러 기능이 전부RequestDto/ResponseDto를 써서 스키마 이름이 충돌하거나 자동 넘버링(RequestDto_1)된다. 프런트엔드가 이 스펙으로 TypeScript 타입을 생성하므로, 접두어가 없으면 생성되는 타입 이름도 함께 망가진다.