Programming/Spring

[스프링] 스프링 가이드 : 패키지 구조

nayoungs 2024. 8. 28. 17:25
728x90

개발표준정의서, 특히 패키지 구조에 대한 가이드를 정립하면서 어떤 패키지 구조가 가장 Best Practice일까에 대한 고민이 많았다. 오늘은 패키지 구조에 대해 잘 정리된 글이 있어 이 글을 기반으로 정리해보는 시간을 가져보려 한다.

 

패키지 구성은 크게 레이어 계층형도메인형 이렇게 2가지 유형으로 나눠 생각해볼 수 있다. 각 유형별로 간단하게 알아보고, Best Practice를 고민해보자.

 

계층형

계층형 구조는 각 계층을 대표하는 디렉터리를 기준으로 코드들이 구성된다.

└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── DemoApplication.java
    │   │               ├── const
    │   │               ├── controller
    │   │               ├── domain
    │   │               ├── dto
    │   │               ├── exception
    │   │               ├── mapper 
    │   │               └── service
    │   └── resources
    │       └── application.properties

계층형 구조의 장점은 해당 프로젝트에 이해가 상대적으로 낮아도 전체적인 구조를 빠르게 파악할 수 있다는 점이다. 단점으로는 디렉터리에 클래스들이 너무 많이 모이게 된다는 점이 있다.

 

 

도메인형

도메인형 구조는 도메인 디렉터리를 기준으로 코드를 구성한다.

─ src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── DemoApplication.java
    │   │               ├── coupon
    │   │               │   ├── controller
    │   │               │   ├── const
    │   │               │   ├── domain
    │   │               │   ├── dto
    │   │               │   ├── exception
    │   │               │   ├── mapper
    │   │               │   └── service
    │   │               ├── member
    │   │               │   ├── controller
    │   │               │   ├── const
    │   │               │   ├── domain
    │   │               │   ├── dto
    │   │               │   ├── exception
    │   │               │   ├── mapper
    │   │               │   └── service
    │   │               └── order
    │   │                   ├── controller
    │   │                   ├── domainconst
    │   │                   ├── domain
    │   │                   ├── dto
    │   │                   ├── exception
    │   │                   ├── mapper
    │   │                   └── service
    │   └── resources
    │       └── application.properties

도메인형의 장점은, 관련된 코드들이 응집해 있다는 점이다. 단점으로는 프로젝트에 대한 이해도가 낮을 경우 전체적인 구조를 파악하기 어렵다는 점이 있다.

 

 

개인적인 Best Practices

참조한 포스팅의 필자와 마찬가지로, 나 역시 도메인 중심의 디렉터리 구조가 더 나은 선택이라고 생각한다. 이 구조를 선택하는 데에는 관습적인 요소도 있지만, 최근에는 개발 표준을 정립하면서도 도메인형 디렉터리 구조가 더 효과적이라는 생각을 하고 있다.

복잡성의 증가 : 계층형 구조의 한계

계층형 구조에서는 Controller나 Service 계층에 너무 많은 클래스가 몰리면서, 복잡도가 급격히 상승한다. 프로젝트 초기에는 상위 디렉토리 몇 개만으로 전체적인 구조를 쉽게 파악할 수 있을지 모르지만, 클래스가 30~40개 이상씩 늘어남에 따라 이러한 단순함은 금새 사라진다. 예를 들어 xxxController , xxxService 와 같은 이름으로 길게 나열된 클래스들은 개별적으로는 이해하기 쉬울 수 있으나, 전체 시스템의 맥락에서 이들을 통합적으로 이해하는 것은 매우 어려워진다.

코드 응집의 중요성 : 도메인형 구조의 강점

반면, 도메인형 구조는 관련된 코드들을 하나의 도메인 안에 모아두기 때문에, 이러한 복잡성을 효과적으로 완화할 수 있다. 관련된 코드가 한 곳에 모여있으면, 자연스럽게 연관된 코드 스타일, 변수 이름, 클래스 구조 등을 공유하게 되고, 이는 일관된 코딩 스타일과 패턴을 유지하는 데 큰 도움이 된다. 결과적으로 개발자들이 서로의 코드를 더 쉽게 이해하고 유지 보수할 수 있는 환경이 조성될 수 있다고 생각한다.

.

 

도메인형 디렉터리 구조

SM처럼 이미 운영 중인 프로젝트에서는 상황에 따라 유연하게 접근하는 것이 필요할 수 있다. 그러나 SI를 진행할 때는 설계 단계에서 패키지 구조를 체계적으로 정립할 시간이 주어지기 때문에, 최적의 방안을 고민할 여지가 더 많아지는 것 같다. 이 과정에서 무엇이 Best Practice인지 깊이 생각해보게 된다.

 

도메인 중심으로 디렉터리를 구성할 경우, 새로운 고민거리가 생길 수 있다. 바로 config, util, error, common, infra 와 같이, 도메인에 속하지 않은 항목들을 어디에 배치할 지에 대한 문제이다. 도메인 중심 구조라는 큰 틀 안에서는 이러한 요소들을 조금 더 유연하게 구성해도 무방하다고 생각한다.

 

참조한 포스팅에서는 다음과 같이, 도메인에 속하지 않는 코드를 global, infra 로 나누어 관리하는 방식을 제시하였다.

전체적인 구조

다음은 참조한 포스팅에서 설명하고 있는 구조이다. 직접 해당 포스팅에서 확인해보는 것도 추천한다.

└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── spring
    │   │           └── guide
    │   │               ├── ApiApp.java
    │   │               ├── SampleApi.java
    │   │               ├── domain
    │   │               │   ├── coupon
    │   │               │   │   ├── api
    │   │               │   │   ├── application
    │   │               │   │   ├── dao
    │   │               │   │   ├── domain
    │   │               │   │   ├── dto
    │   │               │   │   └── exception
    │   │               │   ├── member
    │   │               │   │   ├── api
    │   │               │   │   ├── application
    │   │               │   │   ├── dao
    │   │               │   │   ├── domain
    │   │               │   │   ├── dto
    │   │               │   │   └── exception
    │   │               │   └── model
    │   │               │       ├── Address.java
    │   │               │       ├── Email.java
    │   │               │       └── Name.java
    │   │               ├── global
    │   │               │   ├── common
    │   │               │   │   ├── request
    │   │               │   │   └── response
    │   │               │   ├── config
    │   │               │   │   ├── SwaggerConfig.java
    │   │               │   │   ├── properties
    │   │               │   │   ├── resttemplate
    │   │               │   │   └── security
    │   │               │   ├── error
    │   │               │   │   ├── ErrorResponse.java
    │   │               │   │   ├── GlobalExceptionHandler.java
    │   │               │   │   └── exception
    │   │               │   └── util
    │   │               └── infra
    │   │                   ├── email
    │   │                   └── sms
    │   │                       ├── AmazonSmsClient.java
    │   │                       ├── SmsClient.java
    │   │                       └── dto
    │   └── resources
    │       ├── application-dev.yml
    │       ├── application-local.yml
    │       ├── application-prod.yml
    │       └── application.yml

전체적인 구조는 도메인을 담당하는 디렉터리 domain, 전체적인 설정을 관리하는 global , 외부 인프라를 관리하는 infra를 기준으로 생각해볼 수 있다.

Domain

├── domain
│   ├── member
│   │   ├── api
│   │   │   └── MemberApi.java
│   │   ├── application
│   │   │   ├── MemberProfileService.java
│   │   │   ├── MemberSearchService.java
│   │   │   ├── MemberSignUpRestService.java
│   │   │   └── MemberSignUpService.java
│   │   ├── dao
│   │   │   ├── MemberFindDao.java
│   │   │   ├── MemberPredicateExecutor.java
│   │   │   ├── MemberRepository.java
│   │   │   ├── MemberSupportRepository.java
│   │   │   └── MemberSupportRepositoryImpl.java
│   │   ├── domain
│   │   │   ├── Member.java
│   │   │   └── ReferralCode.java
│   │   ├── dto
│   │   │   ├── MemberExistenceType.java
│   │   │   ├── MemberProfileUpdate.java
│   │   │   ├── MemberResponse.java
│   │   │   └── SignUpRequest.java
│   │   └── exception
│   │       ├── EmailDuplicateException.java
│   │       ├── EmailNotFoundException.java
│   │       └── MemberNotFoundException.java
│   └── model
│       ├── Address.java
│       ├── Email.java
│       └── Name.java

model 디렉터리는 Domain Entity 객체들이 공통적으로 사용할 객체들로 구성된다. 대표적으로 Embeddable 객체, Enum 객체 등이 있다.

infra

└── infra
    ├── email
    └── sms
        ├── AmazonSmsClient.java
        ├── KtSmsClient.java
        ├── SmsClient.java
        └── dto
            └── SmsRequest.java

infra 디렉터리는 infrastructure와 관련된 코드들로 구성된다. infrastructure는 대표적으로 이메일 알림, SMS 알림 등 외부 서비스에 대한 코드들이 존재한다.

 

 

Reference

 

728x90