Spring/Spring

[Spring] 도메인형 패키지 구조로의 전환

JoonYong 2024. 6. 1. 19:28

 

1. Overview

이번 포스트에서는 최근 스프링 부트 프로젝트에서 도메인형 패키지 구조를 사용해본 경험을 공유하려고 합니다. 기존에는 계층형 패키지 구조를 주로 사용했었는데, 이번에 팀 프로젝트를 진행하면서 도메인형 패키지 구조를 도입하게 되었고, 그 이유와 결과를 설명해보겠습니다.

 

2. 계층형 패키지 구조란?

먼저, 계층형 패키지 구조에 대해 간단히 설명해보겠습니다. 계층형 패키지 구조는 일반적으로 다음과 같이 구성됩니다:

com.joon.project
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
├── repository
│   └── UserRepository.java
├── model
│   └── User.java
├── dto
│   ├── request
│   │   └── CreateUserRequest.java
│   └── response
│       └── UserResponse.java
└── exception
    └── UserNotFoundException.java

이 구조는 각 계층별로 패키지를 나누어, 역할에 따라 클래스를 배치하는 방식입니다. 예를 들어, 모든 컨트롤러는 controller 패키지에, 모든 서비스 클래스는 service 패키지에, 모든 리포지토리는 repository 패키지에 위치하게 됩니다.

 

3. 도메인형 패키지 구조란?

도메인형 패키지 구조는 각 도메인별로 패키지를 나누어, 해당 도메인과 관련된 모든 클래스를 같은 패키지에 배치하는 방식입니다. 이번 프로젝트에서는 도메인형 패키지 구조를 다음과 같이 구성했습니다:

com.joon.project
├── domain
│   ├── account
│   │   ├── controller
│   │   │   ├── dto
│   │   │   │   ├── request
│   │   │   │   │   └── CreateAccountRequest.java
│   │   │   │   └── response
│   │   │   │       └── AccountResponse.java
│   │   │   └── AccountController.java
│   │   ├── service
│   │   │   └── AccountService.java
│   │   ├── model
│   │   │   └── Account.java
│   │   ├── repository
│   │   │   └── AccountRepository.java
│   │   └── exception
│   │       └── AccountNotFoundException.java
│   ├── profile
│   │   ├── controller
│   │   │   ├── dto
│   │   │   │   ├── request
│   │   │   │   │   └── CreateProfileRequest.java
│   │   │   │   └── response
│   │   │   │       └── ProfileResponse.java
│   │   │   └── ProfileController.java
│   │   ├── service
│   │   │   └── ProfileService.java
│   │   ├── model
│   │   │   └── Profile.java
│   │   ├── repository
│   │   │   └── ProfileRepository.java
│   │   └── exception
│   │       └── ProfileNotFoundException.java
│   └── ...
└── global
    ├── config
    │   └── SwaggerConfig.java
    ├── security
    │   ├── JwtTokenProvider.java
    │   ├── JwtAuthenticationFilter.java
    │   └── SecurityConfig.java
    └── exception
        ├── GlobalExceptionHandler.java
        └── CustomException.java

이 구조는 각 도메인(예: account, profile 등)에 관련된 모든 요소를 한 패키지에 모아놓습니다. global 패키지에는 프로젝트 전반에 걸쳐 공통적으로 사용되는 설정, 보안, 예외 처리, 유틸리티 클래스 등이 포함됩니다.

 

3-1) 패키지 구조 설명

domain 패키지

  • controller 패키지: Spring MVC 컨트롤러 클래스들이 위치합니다. dto 폴더에는 요청과 응답 객체가 포함됩니다.
    • request: 클라이언트로부터 오는 요청 데이터를 담는 객체들입니다.
    • response: 클라이언트에게 반환되는 응답 데이터를 담는 객체들입니다.
    • 컨트롤러 클래스: HTTP 요청을 처리하고, 요청 데이터를 서비스 레이어로 전달하며, 서비스로부터 받은 데이터를 응답 객체로 변환하여 클라이언트에게 반환합니다.
  • service 패키지: 서비스 클래스들이 위치합니다. 이 클래스들은 비즈니스 로직을 구현하며, 데이터의 가공 및 처리를 담당합니다.
  • model 패키지: 도메인 모델 또는 엔티티 클래스들이 위치합니다. 이 클래스들은 데이터베이스 테이블과 매핑되는 JPA 엔티티들로, 데이터베이스에서 직접 데이터를 가져오거나 저장할 때 사용됩니다.
  • repository 패키지: JPA 리포지토리 인터페이스들이 위치합니다. 리포지토리 인터페이스들은 CRUD 연산을 위한 메서드들을 정의하며, JPA를 통해 데이터베이스와 상호작용합니다.
  • exception 패키지: 애플리케이션에서 사용하는 커스텀 예외 클래스들이 위치합니다. 이 패키지는 비즈니스 로직에서 발생할 수 있는 다양한 예외 상황을 처리하기 위한 클래스를 포함합니다.

global 패키지

  • config 패키지: 프로젝트 전반에 걸친 설정 클래스들이 위치합니다.
  • security 패키지: 보안 관련 클래스들이 위치합니다. 예를 들어, JWT 토큰 처리, 인증 필터, 보안 설정 등이 포함됩니다.
  • exception 패키지: 전역 예외 처리 클래스들이 위치합니다.

 

3-2) 왜 controller 패키지 안에 dto가 있는가?

  • 단일 책임 원칙 (Single Responsibility Principle): dto를 controller 패키지 안에 두면, 컨트롤러와 관련된 요청 및 응답 객체들이 모여 있어 각 레이어의 역할이 명확해집니다. 이는 코드의 가독성과 유지보수성을 높입니다.
  • 모듈화: 각 레이어를 분리하여 모듈화함으로써, 특정 레이어의 변경이 다른 레이어에 미치는 영향을 최소화할 수 있습니다.
  • 편리성: 요청과 응답 객체가 컨트롤러와 가까이 위치함으로써, 컨트롤러가 이러한 객체들을 쉽게 참조하고 사용할 수 있습니다.

 

4. 도메인형 패키지 구조의 장점

  • 높은 응집도: 도메인 관련 모든 요소가 한 곳에 모여 있어 이해하기 쉽고, 관련된 기능들을 한 눈에 파악 가능.
  • 낮은 결합도: 도메인 간의 의존성을 최소화하여, 한 도메인의 변경이 다른 도메인에 미치는 영향을 줄임.
  • 유지보수 용이: 각 도메인의 변경 사항을 독립적으로 관리할 수 있어 유지보수가 용이.
  • 확장성: 새로운 도메인을 추가하기 쉽고, 프로젝트 확장이 간편함.
  • 팀 협업 효율성: 팀원들이 특정 도메인을 담당하여 작업을 분리하기 쉬워 협업 효율성이 높아짐.

 

5. 도메인형 패키지 구조 도입 후 느낀 점

도메인형 패키지 구조를 처음 도입할 때는 낯설고 적응하는 데 시간이 걸렸지만, 각 도메인에 집중하면서 코드 가독성과 유지보수성이 크게 개선되었습니다. 특히 대규모 프로젝트에서 도메인 간의 의존성을 줄이고, 새로운 기능 추가 시 효율성을 높이는 데 유용했습니다. 앞으로도 이 구조를 적극 활용할 계획입니다.