Toy Project를 진행하면서 패키지 구조 개선을 시도하며 공부한 것에 대해 이야기 해보겠습니다.
Package By Layer
Layer 방식으로 패키지를 구성하면 내부에 서로 밀접하게 관련되어있지 않은 클래스들이 모이게 되어 낮은 응집력을 갖게 됩니다. 레이어별 패키징의 예시입니다.
├── com.app
└── controller
├── CompanyController
├── ProductController
└── UserController
└── model
├── Company
├── Product
└── User
└── repository
├── CompanyRepository
├── ProductRepository
└── UserRepository
└── service
├── CompanyService
├── ProductService
└── UserService
└── util
이 구조는 Repository, Service, Controller 패키지간에 높은 결합도를 갖게 됩니다. 또한 요구 사항을 구현하려면 많은 패키지를 변경해야 합니다.
Package By Feature
Feature 방식으로 패키지를 구성하면 각 패키지에는 기능에 필요한 모든 클래스가 포함됩니다. 밀접하게 관련된 클래스를 동일한 패키지에 배치함으로서 독립성을 보장할 수 있습니다.
Feature 패키징 예시입니다.
├── com.app
└── company
├── Company
├── CompanyController
├── CompanyRepository
└── CompanyService
└── product
├── Product
├── ProductController
├── ProductRepository
└── ProductService
└── util
└── user
├── User
├── UserController
├── UserRepository
└── UserService
이 구조는 다른 패키지의 클래스가 서로를 사용하지 않습니다. 패키지 내의 클래스는 서로 밀접하게 연관되어 있기 때문에 응집도가 높고 패키지 간 결합도가 낮습니다.
예시 이외에 추가적인 도메인이 있을 경우 Package by Layer는 Controller, Service, Repository가 서로 다른 단일 패키지에 배치되므로 전체 애플리케이션은 util을 제외하면 세가지 패키지로만 구성되고 각 패키지에는 많은 클래스가 들어있습니다.
하지만 Package by Feature 형식의 경우 도메인별 패키지가 존재하기 때문에 높은 모듈성을 가지게 됩니다.
Package By Feature의 장점
높은 응집력, 낮은 결합 및 높은 모듈성을 가진 패키지를 가진다.
일부 클래스는 접근 제어자를 public 대신 private으로 설정하여 캡슐화가 증가한다.
기능에 필요한 클래스가 패키지 내부에 있으므로 패키지간 탐색 필요성을 줄인다.
Microservice Architecture와 유사한 구조를 가진다. Package By Layer의 경우 애플리케이션의 크기가 커짐에 따라 각 패키지의 클래스가 계속해서 증가한다.
참고자료
https://medium.com/sahibinden-technology/package-by-layer-vs-package-by-feature-7e89cde2ae3a
Spring-PetClinic
Spring 개발자들은 일반적인 Spring Boot 프로젝트가 어떻게 구성되면 좋겠는 지에 대한 예시로 PetClinic이라는 프로젝트를 만들었습니다.
org.springframework.samples.petclinic.model
org.springframework.samples.petclinic.owner
org.springframework.samples.petclinic.system
org.springframework.samples.petclinic.vet
org.springframework.samples.petclinic.visit
각 패키지는 응용 프로그램의 영역, 기능을 나타내며 내부에 고도로 결합된 클래스를 그룹화하고 높은 응집력을 가능하게 합니다.
참고자료
https://github.com/spring-projects/spring-petclinic
결론
다음은 현재 제 Toy Project의 패키지 구조입니다. Package By Layer 방식으로 이루어져 있고 Domain보다는 UI에 대응되는 구조로 나누어져 있습니다.
├── com.domain
└── controller
├── OneononeInquiryController
├── CategoryAdminController
└── AnswerController
└── model
├── Entity
├── Dto
└── mapping
└── repository
├── OneononeInquiryRepository
├── CategoryAdminRepository
└── AnswerRepository
└── service
├── OneononeInquiryService
├── CategoryAdminService
└── AnswerService
└── util
Package By Feature로 구성한다면 다음과 같이 바뀌게 됩니다.
├── com.domain
└── model
├── BaseEntity
└── User
├── User
├── UserController
└── UserRepository
└── UserDto
└── UserMapper
└── UserService
└── Employee
└── OneononeInquiry
├── OneononeInquiry
├── OneononeInquiryDto
└── OneononeInquiryMapper
├── OneononeInquiryRepository
├── OneononeInquiryService
├── OneononeInquiryController
└── Answer
└── CategoryManagement
현재 Toy-Project는 Feature을 명확히 분리해 내기 힘들기 때문에 두번째의 경우 해당 프로젝트를 처음 보는 사람은 전체적인 구조를 파악하기 힘들 거라고 생각합니다.