본문 바로가기

프로젝트/트러블슈팅

[트러블 슈팅] 리팩토링을 통한 복잡했던 모듈 구조를 단순화

도입

지난 글, [트러블 슈팅] 레이어별 멀티 모듈 적용 (과도한 모듈 분리로 실패🤪) 에서 레이어 간 단방향 의존 관계를 갖도록 레이어 별로 모듈 분리를 시도했다. 그러나 과도한 분리로 인해 모듈 구조가 복잡해졌고 결국 실패로 이어졌다. 이번 글에서 복잡했던 멀티 모듈 구조를 리팩토링을 통해 단순화한 경험을 이야기하겠다.

과도하게 분리된 모듈을 하나의 모듈로

계층마다 독립적이고 관심사가 다르므로 모듈화를 통해 응집도를 높이려고 했다. 그리고 레이어드 아키텍처에서 단방향 의존 관계를 가져야 하므로 모듈의 의존 방향을 통해 제약을 걸어주었다.

 

그러나 아키텍처를 모듈과 일치시키려는 강박 때문에 모듈 구조가 너무 복잡해졌다. 그리고 구현에 대한 깊은 이해도 없이 성급하게 모듈화를 진행시켰다. 결국 과도한 추상화로 인해 관리해야할 모듈의 수만 늘어났다.

 

여기서 모든 모듈이 의존하고 있는 common 모듈까지 합치면 모듈이 총 6개이다.

복잡했던 기존 모듈

 

리팩토링을 통해 application, domain, chat, mongo 모두 네 개의 모듈을 하나의 core 모듈에 몰아넣었다. 따라서 관리해야할 모듈이 6개에서 3개로 줄어들었고 의존 관계도 단순화되었다.

 

실행 가능한 모듈이자 presentation 계층인 api 모듈은 MVC와 RestDocs에 대한 의존성을 가지고 있다.

 

core 모듈은 비즈니스 로직이 흐르고 있는 모듈로 내부에서 도메인마다 application, domain, infrastructure 계층이 나뉘고 있다.

그리고 common 모듈을 api로 의존하고 있는데, api 모듈이 common 모듈을 알도록 하기 위함이다.

 

마지막으로 common 모듈은 프로젝트 전체에서 사용할 커스텀 예외, 유틸 클래스를 관리하고 있다.

 

리팩토링하여 단순화된 모듈 구조

프로젝트가 종료되고 진행한 대규모 리팩토링이라 메인이 아닌 개인 레포지토리에서 진행했다. 모듈 구조를 보고싶다면 아래 링크로 들어가보시면 된다. 

https://github.com/heenahan/onedayhero-v2

 

GitHub - heenahan/onedayhero-v2: 원데이 히어로 v2

원데이 히어로 v2. Contribute to heenahan/onedayhero-v2 development by creating an account on GitHub.

github.com

하나의 모듈에 멀티 데이터소스

분리된 모듈을 하나의 모듈에 몰아넣다보니 단일 데이터소스에서 멀티 데이터소스가 되었다.

 

core 모듈의 의존성을 보면 Spring Data JPA, Spring Data MongoDB, Spring Data Redis 이렇게 세 개의 데이터소스가 하나의 모듈에서 존재한다.

dependencies {
    // JPA
    api 'org.springframework.boot:spring-boot-starter-data-jpa'
    
    // MongoDB
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    
    // Redis
    implementation 'org.springframework.data:spring-data-redis'
    implementation 'io.lettuce:lettuce-core'
}

 

이렇게 여러 개의 Spring Data 모듈이 존재할 경우 스프링 서버를 구동시키면 아래와 같은 INFO 로그가 뜬다.

 

로그를 읽어보면, 다중 Spring Data 모듈이 발견되어 엄격한 Repository 설정 모드로 들어간다고 한다. 그리고 이 리포지토리에 대한 저장소(데이터베이스) 할당을 안전하게 식별할 수 없다고 한다. 

 

왜 이런 로그가 뜰까? 스프링부트는 @EnableJpaRepositories가 auto configuration 된다. @EnableJpaRepositories의 Javadoc을 보면 default에서 Spring Data repositories를 스캔한다고 하는데, 이는 JpaRepository 뿐만 아니라 CrudRepository, PagingAndSortingRepository도 스캔 대상이다. 

Annotation to enable JPA repositories. Will scan the package of the annotated configuration class for Spring Data repositories by default.

 

MongoRepository는 CrudRepository, PagingAndSortingRepository를 상속하고 있으므로 결국 @EnableJpaRepositories의 스캔 대상이 된다.

 

MongoRepository 상속 구조

 

로그를 지우고 싶으면 JpaRepository만 스캔하도록 스캔 범위를 지정해주면 된다. 먼저 JpaRepository와 MongoRepsitory 패키지를 분리했다. 모든 서브 패키지의 domain 패키지에 JpaRepsitory를 위치하도록 하였고 이를 스캔 범위로 지정했다.

 

@Configuration
@EnableJpaAuditing
@EnableJpaRepositories(basePackages = {
        "com.sixheroes.onedayherocore.**.domain"
})
@ComponentScan(basePackages = {
        "com.sixheroes.onedayherocore.**.domain"
})
public class JpaAuditingConfiguration {
}

 

마찬가지로 MongoRepository 스캔 범위도 지정해주었다.

@Configuration
@EnableMongoAuditing
@EnableMongoRepositories(basePackages = {
        "com.sixheroes.onedayherocore.**.mongo" // Mongo Entity
})
@ComponentScan(basePackages = {
        "com.sixheroes.onedayherocore.**.mongo"
})
public class MongoConfiguration {
}

 

 

참고