멀티 모듈 탐험기 (4) - 멀티 모듈의 강력함

suky·2024년 4월 5일
0

멀티 모듈 탐험기

목록 보기
4/4
post-thumbnail

안녕하세요~ 요즘 공부하느라 바쁜 suky입니다~ 😁
마지막 멀티 모듈 탐험기에 오신 여러분을 환영합니다~! 👏👏👏👏👏
이번에는 다양한 측면에서 멀티 모듈의 강력함을 느껴볼 시간을 가질 예정입니다.
객체 지향에 있어서 모듈 단위로 프로젝트를 바라보는 것에 대해 큰 이점이 있는 것이 전달되었으면 좋겠습니다 ㅎㅎ,,,,
아직 저도 슈퍼주니어 개발자라 못 깨달은 영역도 많은 것 같고, 복잡도 관리가 어렵긴 하지만! 분명한 이점이 있음은 틀림없는 것 같습니다.

멀티 모듈의 강력함

1. 중복 코드를 줄일 수 있다.

멀티 모듈의 강력함 첫 번째는 중복 코드를 줄일 수 있다.입니다.
저도 현업에서 싱글 모듈 베이스의 프로젝트를 진행하면서 의아했던 부분이 있습니다. 같은 도메인을 다루는 Batch, API가 다른 프로젝트 파일로 이루어져 있어서 도메인에 대한 중복된 코드가 발생한 것이었죠. 가령 다음 구조를 생각해 볼 수 있습니다.

user-api
ㄴ domain
  ㄴ User

user-batch
ㄴ domain
  ㄴ User

user-apiuser-batchUser 도메인에 대해서 접근을 합니다. Userage라는 필드가 추가된다면 user-apiuser-batch 프로젝트에서는 모두 변경이 이뤄져야하죠. 개념적으로 동일한 개념임을 알 수 있습니다. 만약 user-apiUser를 수정하면서 user-batch의 값을 변경하지 않으면 논리적으로 불일치가 일어날 수 있습니다. 이 사이드 이펙트가 어느 정도의 파급을 일으킬지는 아무도 모릅니다.

user-core
ㄴ domain
  ㄴ User
  
user-api
=> implementation project(":user-core")
user-batch
=> implementation project(":user-core")

위와 같이 domainuser-core 모듈로 분리하고 user-apiuser-batch에서 참조를 하도록 하면 어떨까요?
user의 상태가 변화하면 user-apiuser-batch에 즉각적으로 반영이 되고, 테스트에서 로직상 위험을 최대한 빨리 캐치할 수 있을 것이라고 생각합니다! 😁

domain을 예시로 들었지만 이 외에 범용적인 유틸 객체를 user-common과 같은 모듈로 묶어서 관리하는 예시도 있을 수 있겠네요 😘

한 줄로 요약하자면 다음과 같겠네요.

수평적인 관심사를 모듈로 분리함을 통해 중복을 줄일 수 있다.

2. 캡슐화를 통해 결합도를 낮추고, 응집도를 높일 수 있다.

Loose Coupling, High Cohesion는 더 나은 코드를 위한 불문율이라고 할 수 있습니다. 1에서 언급한 중복을 줄이는 것도 이에 대한 일환으로 볼 수 있죠. 싱글 모듈과 멀티 모듈의 의존성 관리를 확인하면서 멀티 모듈의 강려크함을 느껴보도록 합시다!

2.1. 멀티 모듈로 의존성 분리하기

싱글 모듈로 프로젝트를 구성하는 것을 생각해보죠. Web, JPA, MySQL, H2, Lombok 등에 대한 의존성을 하나의 build.gradle에 작성할 수 밖에 없을 것 입니다.

// build.gradle
dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  runtimeOnly 'com.h2database:h2'
  runtimeOnly 'com.mysql:mysql-connector-j'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

이제 프로젝트 아키텍처를 나눠서 생각을 해보도록 하죠. 음,,, 일단은 순수한 도메인과 컨트롤러만 분리하도록 해볼까요? 공통된 기능은 root 프로젝트의 build.gradle에 넣도록 하죠.

// domain build.gradle
dependencies {
}

// api build.gradle
dependencies {
  implementation project(':domain')
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  runtimeOnly 'com.h2database:h2'
  runtimeOnly 'com.mysql:mysql-connector-j'
}

// (root) build.gradle
dependencies [
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
}

아무런 모듈을 의존하지 않는 순수한 domain과 비지니스 로직을 포함하는 api를 나눌 수 있었습니다! 😁
이제 api 모듈 내에서 영속성을 관리하는 부분을 storage 모듈로 분리를 해볼까요?

// domain build.gradle
dependencies {
}

// storage build.gradle
dependencies {
  implementation project(':domain')
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  runtimeOnly 'com.h2database:h2'
  runtimeOnly 'com.mysql:mysql-connector-j'
}

// api build.gradle
dependencies {
  implementation project(':domain')
  implementation project(':storage')
  implementation 'org.springframework.boot:spring-boot-starter-web'
}

// (root) build.gradle
dependencies [
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
}

와~ storage 모듈에는 영속성 관련하여 JPA, MySQL, H2 등의 의존성을 의존하는 것을 볼 수 있습니다. 영속성 관련 모듈의 특성답게 의존성을 의존하고 있네요.

아키텍처를 어떻게 개념적으로 구분짓느냐에 따라 더 나눌 수 있겠지만, 이젠 나누는 것은 그만하고 서비스에 변화에 대해 어떻게 유연하게 대처하는지 생각해보겠습니다.

2.2. 모듈로 캡슐화하기

이제 서비스가 날로날로 잘 되어서 Redis를 도입한다고 생각해보겠습니다. 싱글 모듈 프로젝트였다면 단순히 implementation 'org.springframework.boot:spring-boot-starter-data-redis'를 의존성에 명시해야겠네요.

// build.gradle
dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'org.springframework.boot:spring-boot-starter-data-redis' # 추가
  runtimeOnly 'com.h2database:h2'
  runtimeOnly 'com.mysql:mysql-connector-j'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

싱글 모듈 상황에서 이렇게 된다면 implementation 'org.springframework.boot:spring-boot-starter-data-redis'에 대한 의존성 관리에 대한 책임이 domain, api 영역에도 영향을 미치게 됩니다. 만약 의존성이 충돌이 난다면,,, 골치 아파지겠네요 😥
영속성에 대한 의존성이 다른 곳에 영향을 끼치니,,, 불필요한 사이드이펙트가 발생하는 것 같습니다.

다음은 멀티 모듈에서 의존성을 추가하는 것입니다.

// domain build.gradle
dependencies {
}

// storage build.gradle
dependencies {
  implementation project(':domain')
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  implementation 'org.springframework.boot:spring-boot-starter-data-redis' # 추가
  runtimeOnly 'com.h2database:h2'
  runtimeOnly 'com.mysql:mysql-connector-j'
}

// api build.gradle
dependencies {
  implementation project(':domain')
  implementation project(':storage')
  implementation 'org.springframework.boot:spring-boot-starter-web'
}

// (root) build.gradle
dependencies [
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
}

어때요? 영속성을 관리하는 storage 모듈에만 추가되는 것이 깔끔하지 않나요? storage를 의존하고 있는 api 모듈에서는 storage와 인터페이스만 일치한다면 어떤 변화가 일어났는지 모릅니다. 완벽히 캡슐화되어 있기 때문이죠. 심지어 JPAMyBatis 혹은 JDBC를 이용하는 것으로 바꾸어도 말이죠.

즉, 멀티 모듈은 캡슐화를 통해 Loose Coupling, High Cohesion을 보장할 수 있습니다.

BIG BIG 단점: 복잡도 증가

There is no silver bullet (은 총알은 없다)라는 말이 있듯이 멀티 모듈은 장점만 있는 것이 아닙니다. 복잡도가 증가한다는 가장 큰 단점이 있습니다.

팀 내 구성원이 멀티 모듈에 대해서 학습하지 않는다면 러닝 커브가 가장 큰 장애물이 될 수 있고, storage 계층과 api 계층을 나눴을 때 트랜잭션 처리를 어떻게 할 것인지 등에 대하여 초기에 학습 비용이 너무 크게 들어갑니다.

산재된 중복으로 인해 프로젝트의 사이드 이펙트 파악이 힘든 경우 vs 멀티 모듈로 인한 프로그램 복잡성 증가 간에 적절한 트레이드 오프를 계산하지 않는다면 이는 도입하지 않느니만 못할 수도 있을 것 같습니다.

마무리

그래도 확실한 것은, 멀티 모듈은 적당한 규모에서도 효과를 발휘할 수 있다고 생각합니다.

저는 현업에서 5개 이상의 프로젝트를 개발 및 유지 보수 중에 있는데요. 암/복호화인증 모듈 등 중복으로 인한 코드가 산재된 경험을 많이 했고, 이를 동기화를 해주기 위해 작업하는 데 힘을 쏟으면서 중복을 줄이는 것에 대한 중요성을 뼛속 깊이 느끼고 있습니다. 😥
만약 아예 별개의 프로젝트면 중복되는 모듈을 따로 배포하여 사내 레포지토리에서 의존성을 주입받으면 되겠지만, 같은 도메인이라면 멀티 모듈 프로젝트에서 모듈 단위로 관리하는 것이 더 좋다고 현재 생각을 하고 있습니다.

이번 멀티 모듈 탐험기가 독자 여러분께 도움이 되었기를 바라며 탐험기를 이만 마치겠습니다~😁

profile
도전하고 성장하는 개발자입니다 :)

0개의 댓글