프로젝트에 적합한 패키지 모듈화
들어가며
사이드 프로젝트를 시작하면서 요구사항을 정리하고 이제 코드로 구현할 차례가 되었다. 그런데 막상 코드를 치려니 빈 프로젝트에 클래스를 어떻게 묶고 패키지를 나눠야 할지 막막했다.
내 프로젝트에 적합한 구조는 뭘까? 정리한 도메인은 크게 회원, 피드, 음악 정도로 소규모 프로젝트이다. 하지만 기본적인 기능 구현이 끝나면 추가로 더 확장할 계획이다.
이러한 상황을 바탕으로 패키지 구조를 모듈화하는 방법에 대해 알아보자.
패키지 모듈화
는 크게 두 가지 접근법이 있다.
레이어에 따른 패키지 구조
app
├── controller
│ ├── OrderController
│ ├── ProductController
│ └── ...
├── model
│ ├── Order
│ ├── Product
│ └── ...
├── service
│ ├── OrderService
│ ├── ProductService
│ └── ...
└── repository
├── OrderRepository
├── ProductRepository
└── ...
레이어별 패키지 구조는 클래스를 같은 계층별로 분류하여 프로젝트를 구성한다. 이러한 방식은 책임 분담이 명확하고 프로젝트 전체를 파악하기에 용이하다. 하지만 서로 밀접하지 않은 클래스들이 같은 레이어의 패키지로 묶임으로써 패키지 내 응집력이 떨어지게 된다.
장점
- 소규모 프로젝트라 기능이 복잡하지 않은 경우 계층별로 빠르게 적용하고 전체를 파악하기 쉽기 때문에 적용하기 적합하다.
- 도메인에 대한 이해없이 처음 시작하는 사람도 클래스를 추가하기 쉽다.
단점
- 프로젝트가 커지면 더 많은 기능을 통합하게 되면서 복잡성이 증가한다.
- 캡슐화 측면에서 불리하다. 연관이 있는 클래스들이 레이어에 따라 분리되므로 public으로 열어야 한다.
기능에 따른 패키지 구조
app
├── order
│ ├── OrderController
│ ├── OrderService
│ └── OrderRepository
└── product
├── ProductController
├── ProductService
└── ProductRepository
기능별로 패키지를 묶어 그 안에 다양한 컴포넌트와 모듈을 구성한다. 밀접하게 관련된 클래스는 같은 패키지에 배치해서 모듈의 독립성을 보장한다.
장점
- monolith 서비스에서 각 팀별로 서로 다른 모듈을 작업함으로써 팀 간의 충돌과 종속성을 줄일 수 있다.
- 기존 기능을 추출해서 microservice로 전환하기에도 유리하다.
- 기능별로 묶여 있기 때문에 외부에 노출해야 하는 기능만 public으로 열 수 있다 ➡️ 캡슐화
단점
- 기능이 추가되면 패키지 수 또한 많아진다
- 새로운 클래스를 추가할 때 어떤 패키지에 들어갈지 정확히 알아야 한다. ➡️ 새로운 사람이 작업하기에 어렵다
응집도와 결합도
유지보수 및 수정 비용을 절감하는 좋은
소프트웨어일수록 모듈의 독립성이 높다.
모듈의 독립성은 응집도와 결합도의 정도로 측정된다. 독립성이 높은 모듈
일수록 응집도가 강하고 결합도는 느슨하다.
즉, 패키지 구조를 설계할 때에는 모듈을 어떻게 구성해야 응집도가 강하고 결합도가 느슨해질 지 고려해야 한다.
응집도
응집도는 한 모듈 내부의 구성 요소들이 서로 연관된 정도를 의미한다.
응집도가 강하면 한 모듈의 변경될 때 다른 모듈의 변경이 적기 때문에 시스템의 유지보수성이 향상된다. 반대로 응집도가 약하면 모듈의 독립성이 떨어지고 재사용성과 이해도도 현저히 떨어지게 된다.
결합도
결합도는 모듈 간의 상호 의존 정도를 의미한다.
따라서 결합도가 높으면 한 모듈을 변경했을 때 의존하는 다른 모듈들도 영향을 받기 때문에 유지보수가 어려워진다. 반대로 결합도가 낮으면 요구사항으로 인한 모듈의 변경이 다른 모듈에 영향을 미치지 않기 때문에 유지보수가 쉬워진다.
핵심은 서로 연관이 있는 컴포넌트끼리는
가깝게
, 관련 없는 컴포넌트는멀리
배치한다
내가 선택한 패키지 구조
현재 작업하는 뮤직 소울메이트
프로젝트는 규모는 작지만 다음과 같은 특징 때문에 기본적으로 기능에 따른 패키지 구조를 따르기로 했다.
- 멘토링 과정에서 빠르게 기능 하나를 구현하고 피드백을 받기 위해서는 기능에 따른 패키징이 더 적합하다.
- 현재 정리된 요구사항에서는 다른 모듈과 연관되는 작업이 크게 요구되지 않는다. 따라서 굳이 연관이 없는 클래스들을 같은 레이어끼리 묶어놓을 필요가 없다. (높은 응집도, 낮은 결합도)
- 추후에 기능이 확장되고, 필요와 상황에 따라 마이크로서비스로 전환할 가능성이 있다.
- 혼자 하는 프로젝트이기에 처음 시작하는 사람이 프로젝트를 파악하기 어렵다는 기능 기반 패키징의 단점은 적용되지 않는다.
기본적으로는 기능에 따라 패키징을 하되, 여러 모듈이 공통으로 사용할만한 컴포넌트는 common이라는 패키지를 만들고 그 하위에 모아두었다.
- 공통 처리( layer based ) : 공통으로 사용하거나 외부 모듈인 경우
- util, config, security
- 기능별 모듈
- member: domain, service, web, exception으로 분리. 그 안에서도 서로 연관이 높은 컴포넌트끼리 묶는다. entity와 repository는 domain의 하위로 묶고, dto와 controller는 web으로 묶는다.
결론
‘은탄환은 없다’는 프레더릭 브룩스의 말처럼 모든 상황에 맞는 하나의 완벽한 해답은 없다.
이 프로젝트 역시 현재 상황에 최대한 적합하도록 위와 같이 적용했지만, 기능이 고도화되고 현재의 구조가 불편해진다면 당연히 그에 맞게 프로젝트의 구조는 또 변화할것이다.
이번에 패키지 구조 설계에 대해 알아봄으로써 앞으로 프로젝트를 시작할 때 현재 상황에 맞는 방식이 무엇인지 조금은 명확하게 알고 시작할 수 있을 것 같다.
댓글남기기