이 글은 프로젝트 패키지 구조에 대해서 고민을 하고 자료를 찾아보면서
ChatGPT에 질문을 하고 받은 답변을 정리한 내용입니다.
잘못된 정보가 있을 수 있으니 알려주시면 수정하도록 하겠습니다.
감사합니다.
✅ SpringBoot 프로젝트 패키지 구조
Spring Boot 프로젝트에서 패키지 구조를 도메인 기준으로 나눌지, 계층별(레이어별)로 나눌지는 설계 철학과 프로젝트의 복잡도, 팀 규모, 유지보수 전략에 따라 달라질 수 있는 중요한 선택입니다.
두 가지 구조의 개요
1. 계층(Layered) 패키지 구조
arduino
CopyEdit
com.example.project
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
├── dto
├── config
└── ...
2. 도메인(Domain/Feature) 패키지 구조
arduino
CopyEdit
com.example.project
├── user
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ ├── dto
│ └── entity
├── product
│ ├── ProductController.java
│ ├── ProductService.java
│ └── ...
└── common
├── config
└── exception
장단점 비교
항목 계층 구조 (Layered) 도메인 구조 (Domain-centric)
초기 진입 장벽 | 낮음. 익숙하고 직관적임 | 처음 보면 익숙하지 않을 수 있음 |
구조 명확성 | 계층별 역할이 명확히 보임 | 각 도메인의 책임이 명확하게 보임 |
파일 위치 검색 | 역할 기반으로 빠르게 찾기 쉬움 | 도메인 기준으로 묶여 있으므로 의미 파악 쉬움 |
변경 영향 추적 | 기능 변경 시 여러 패키지를 수정해야 함 | 대부분 한 패키지 내에서 변경 완료 가능 (높은 응집도) |
대규모 프로젝트 확장성 | 구조가 커질수록 유지보수 어려움 | 모듈화가 쉬워지고, 마이크로서비스로 전환도 쉬움 |
테스트 및 리팩터링 | 단위 테스트나 변경 파악이 어렵기 쉬움 | 단위 테스트 작성 및 모듈 분리가 쉬움 |
공통 기능 공유 | 공통 계층에 기능 넣기 쉬움 | 공통 모듈을 별도로 분리해야 함 |
실제로는 어떤 구조를 추천하는가?
소규모 프로젝트 or 빠른 MVP 개발
- 계층 구조(Layered) 추천
- 이해하기 쉽고, 초반 속도 빠름
- 개발자 간 역할 분담도 쉬움
중대형 프로젝트 or 팀 협업, 유지보수 중심
- 도메인 구조(Domain-centric / 헥사고날, DDD 등) 강력 추천
- 각 도메인 기능이 명확히 분리되고, 테스트/확장성/유지보수가 탁월함
- 마이크로서비스로 분리하거나 모듈화 전환 시 유리
실무에서는 절충 구조도 많음
예를 들어, 아래처럼 도메인 기반으로 묶되, 내부적으로 역할 구분을 유지하는 방식도 자주 사용됩니다:
markdown
CopyEdit
com.example.project
└── user
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
└── dto
이렇게 하면:
- 도메인 응집도는 유지
- 역할별로 구조도 구분되어 가독성 확보
결론
질문 추천
"Controller, Service 패키지로 계층별로 나눌까?" | 소규모, 단기 프로젝트 or 빠른 MVP용으로는 적합 |
"도메인 기준으로 묶어 구성하는 게 좋을까?" | 장기 유지보수, 규모 확장, 도메인 책임 분리 측면에서 훨씬 유리 |
"둘 다 적절히 쓸 수 있을까?" | ✅ 가능! 도메인 기반 + 내부 역할 구분 조합이 실무에서 가장 많이 사용 |
✅ 스프링부트 프로젝트 구조 설계 시 참조해야 할 핵심 요소
Spring Boot 프로젝트의 구조를 설계할 때는 단순히 "파일을 어디에 놓을까?"를 넘어서, 프로젝트의 복잡도, 도메인 응집도, 유지보수성, 확장 가능성 등을 고려하여 설계해야 합니다. 구조 설계는 전체 프로젝트의 생산성과 팀워크에 큰 영향을 주기 때문에 초기 단계에서 충분히 고민하고 정리해두는 것이 매우 중요합니다.
1. 도메인 기반 or 계층 기반 패키징 선택
구조 방식 설명
계층 기반 (Layered) | controller, service, repository 등 기능별로 나눔 |
도메인 기반 (Domain-centric, feature-based) | user, product, order 등 도메인 단위로 나눔 |
혼합형 구조 | 도메인 기준으로 묶되, 내부에서 역할별로 나눔 |
추천: 소규모면 계층 기반도 괜찮지만, 중대형 규모 이상은 도메인 기반 구조가 유지보수성과 확장성 측면에서 유리함.
2. 관심사의 분리 (Separation of Concerns)
- 비즈니스 로직, API 처리, DB 접근, 예외 처리, 설정 등은 명확히 역할을 나눠야 합니다.
예시 구성
arduino
CopyEdit
com.example.project
├── config // 환경 설정 클래스
├── common // 공통 유틸, 예외, 응답 객체 등
├── user // 도메인 패키지
│ ├── controller
│ ├── service
│ ├── repository
│ ├── dto
│ └── entity
3. 의존성 방향을 고려한 구조 (의존성 역전)
- Controller → Service → Repository 방향으로 의존성 흐름을 단방향으로 유지
- Service가 Controller에 의존하거나, Repository가 Service를 호출하는 역참조는 금지
- DDD나 클린 아키텍처에서는 도메인 계층이 외부에 의존하지 않도록 함
4. 테스트 가능한 구조
- 각 계층이 단일 책임(SRP)을 가지면 테스트 코드 작성이 쉬워짐
- Service를 Mock 하여 Controller 테스트, Repository를 Mock 하여 Service 테스트 가능
구조 설계 시, "테스트 코드를 쉽게 작성할 수 있는가?"를 고려해야 함
5. Config 클래스 분리
- 외부 설정값, 필터, CORS, 보안 설정 등은 config 패키지로 분리
- @Configuration, @EnableXXX, @Bean 정의 등은 따로 모아야 유지보수 용이
6. DTO, Entity, Mapper 역할 분리
구성요소 설명
DTO | Controller ↔ Service 간의 전송 객체 |
Entity | DB 테이블 매핑용 (JPA) 객체 |
Mapper / Converter | DTO ↔ Entity 변환 로직 분리 (MapStruct 또는 수동 매핑) |
7. 공통 예외 처리 구조
- 전역 예외 처리: @RestControllerAdvice, @ExceptionHandler
- 커스텀 예외 클래스 분리: CustomException, ErrorCode
8. 보안, 로깅, 캐싱, 인터셉터 등의 구조화
- AOP를 활용한 로깅 처리
- Filter / Interceptor 구조화
- CacheManager를 사용하는 캐싱 전략
- Security 설정 분리 (SecurityConfig, JwtProvider 등)
9. API 버전 관리 구조 고려
프로젝트가 외부에 오픈되거나 장기 운영되는 경우, 버전 관리 필요
pgsql
CopyEdit
com.example.project.api.v1.user.UserController
com.example.project.api.v2.user.UserController
10. 환경별 설정 관리
- application.yml, application-dev.yml, application-prod.yml 등 프로파일 분리
- @Profile을 이용한 Bean 분리
- Secret/Key는 Spring Cloud Config 또는 환경 변수로 관리
설계 시 자주 실수하는 항목
실수 이유
모든 클래스를 한 패키지에 몰아넣음 | 구조가 커질수록 유지보수 불가능 |
DTO/Entity 혼용 | 역할 혼동, 보안 이슈 발생 |
공통 기능 중복 | 공통 예외/응답 구조 없음 |
기능에 비해 과도한 계층화 | 불필요한 복잡성 유발 |
테스트 구조 고려 안 함 | TDD/단위 테스트 어려움 |
추천 참고 자료
- Spring 공식 문서 - Best Practices
- Spring Boot Sample Projects (spring-projects/spring-petclinic 등)
- DDD 설계 참고: “Implementing Domain-Driven Design” by Vaughn Vernon
- Clean Architecture 참고: “Clean Architecture” by Robert C. Martin (Uncle Bob)
🔚 마무리 요약
"프로젝트 구조 = 설계 철학 + 유지보수 전략" 규모가 커질수록 도메인 중심 구조가 강력한 장점을 가지며, Spring에서도 최근에는 DDD 스타일(도메인 중심 구조)을 권장하는 흐름입니다. 작고 단순한 프로젝트에서는 계층 구조도 무방하지만, 유지보수와 확장 가능성을 고려하면 도메인 중심 구조로 가는 것을 추천드립니다.
구조 설계 시 고려사항 요약:
- 구조는 기능 확장, 유지보수, 테스트 용이성을 고려하여 설계
- 가능한 한 도메인 중심 구조 + 관심사 분리
- DTO, Entity, Config, Exception 등은 책임별로 분리
- 팀원 간 공통된 구조 이해와 코드 컨벤션 정립이 중요
✅ 샘플 프로젝트 구조 템플릿
아래는 Spring Boot 기반의 도메인 중심 구조(Domain-centric architecture) 를 기준으로 작성한 샘플 프로젝트 구조 템플릿입니다.
기본적인 구성은 DDD 스타일을 따르되, 실무 적용성을 고려하여 Controller–Service–Repository, DTO–Entity, Config, Exception 등을 분리하였습니다.
pgsql
CopyEdit
com.example.project
├── config
│ ├── SwaggerConfig.java
│ ├── WebConfig.java
│ └── SecurityConfig.java
│
├── common
│ ├── exception
│ │ ├── GlobalExceptionHandler.java
│ │ ├── ErrorCode.java
│ │ └── CustomException.java
│ ├── response
│ │ └── ApiResponse.java
│ └── util
│ └── DateTimeUtils.java
│
├── user
│ ├── controller
│ │ └── UserController.java
│ ├── service
│ │ └── UserService.java
│ ├── repository
│ │ └── UserRepository.java
│ ├── dto
│ │ ├── UserRequestDto.java
│ │ └── UserResponseDto.java
│ ├── entity
│ │ └── User.java
│ └── mapper
│ └── UserMapper.java // DTO ↔ Entity 변환
│
├── product
│ ├── controller
│ ├── service
│ ├── repository
│ ├── dto
│ ├── entity
│ └── mapper
│
└── ProjectApplication.java
각 패키지의 역할 설명
패키지 역할
config/ | WebMVC, Security, Swagger, CORS 등 전역 설정 모음 |
common/exception | 커스텀 예외 처리, 에러코드 분리, 전역 핸들러 |
common/response | 통일된 응답 구조 (ApiResponse<T>) |
common/util | 날짜/문자열 등 재사용 유틸리티 클래스 |
user/ | 도메인별 코드 (여기서는 사용자 도메인) |
controller/ | REST API 엔드포인트, 요청-응답 매핑 |
service/ | 비즈니스 로직 담당 |
repository/ | JPA 인터페이스 및 DB 쿼리 |
dto/ | 클라이언트와의 통신에 사용하는 객체 |
entity/ | DB 테이블과 매핑되는 도메인 객체 |
mapper/ | DTO ↔ Entity 간 변환 (MapStruct 사용 가능) |
ApiResponse 예시
java
CopyEdit
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
public ApiResponse(T data) {
this.success = true;
this.message = "success";
this.data = data;
}
public ApiResponse(String message) {
this.success = false;
this.message = message;
}
// Getter/Setter 생략
}
GlobalExceptionHandler 예시
java
CopyEdit
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity<ApiResponse<Object>> handleCustomException(CustomException ex) {
return ResponseEntity
.status(ex.getErrorCode().getHttpStatus())
.body(new ApiResponse<>(ex.getErrorCode().getMessage()));
}
// Other @ExceptionHandler 추가
}
SwaggerConfig 예시
java
CopyEdit
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("My API")
.version("v1")
.description("Spring Boot API 문서"));
}
}
사용자 도메인 컨트롤러 예시
java
CopyEdit
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
public ApiResponse<UserResponseDto> createUser(@RequestBody UserRequestDto dto) {
return new ApiResponse<>(userService.createUser(dto));
}
}
✅ 도입 시 팁
- 도메인 단위로 패키지 나누기
- 공통/전역 요소(config, exception, util, response)는 분리
- DDD를 적용하고 싶다면 도메인 내부에서 domain, application, infrastructure 식으로 확장 가능
- 추후 마이크로서비스 전환도 고려된다면 모듈화 기반 multi-module project 구조도 고려 가능
'Backend > SpringBoot' 카테고리의 다른 글
SpringBoot에서 preferred-json-mapper 설정의 역할 (2) | 2025.07.09 |
---|
댓글