멀티모듈 프로젝트에서 모듈 간 상호 의존성 문제 해결하기
멀티모듈 프로젝트를 처음 진행하면 예상치 못한 의존성 문제에 직면할 수 있다. 각 모듈이 독립적으로 동작하도록 설계해야 하지만, 기능을 구현하는 과정에서 특정 모듈에서 다른 모듈의 기능이 필요해지는 경우가 발생할 수 있다. 이번 글에서는 모듈 간 상호 의존성 문제가 발생한 사례와 이를 해결한 방법을 공유한다.
문제 발생: Common 모듈에서 특정 테이블 접근 필요
프로젝트를 멀티모듈 구조로 구성했지만, 진행 중에 일부 변경이 있었다. 개발이 진행될수록 기존에 예상하지 못했던 기능이 필요해지고, 이에 따라 설계 수정이 필요하게 되었다. 현재 구조는 다음과 같다.
이러한 구조에서 common
모듈이 특정 테이블에 접근해야 하는 상황이 발생했다. 예를 들어, 사용자의 인증 정보를 가져와야 하는데, 처음에는 아래와 같은 방식으로 접근을 시도했다.
public BaseResponse<Void> logout(@RequestParam String username) {
이 방법은 username을 요청 파라미터로 받아 처리하는 방식이었지만, 보안상 문제가 있었다. 단순히 username만 변경하면 타인이 임의로 로그아웃을 수행할 수 있는 취약점이 존재했다. 이러한 방식은 사용자의 신원을 검증할 수 있는 강력한 인증 절차를 제공하지 않으므로 보안에 취약할 수밖에 없다. 따라서 이 방식은 기각되었다.
해결책: DIP(의존성 역전 원칙) 적용
그렇다면 auth 모듈에서만 가능한 DB 접근을 common 모듈에서도 가능하게 하려면 어떻게 해야 할까?
이 문제를 해결하기 위해 DIP(Dependency Inversion Principle, 의존성 역전 원칙) 을 적용하기로 했다. DIP는 상위 모듈이 하위 모듈에 직접 의존하지 않고, 인터페이스를 통해 의존성을 관리하는 원칙이다. 이를 통해 모듈 간 결합도를 낮추고 확장성을 높일 수 있다.
✅ 인터페이스를 활용한 의존성 역전
먼저, common
모듈에서 인터페이스를 정의한다. 이는 common
모듈이 특정 구현체가 아닌 추상적인 인터페이스에 의존하도록 하기 위함이다.
// common 모듈에서 인터페이스 정의
public interface UserDetailsProvider {
UserDetails loadUserByUsername(String username);
}
그다음, auth
모듈에서 이를 구현한다. 이렇게 하면 실제 구현체는 auth
모듈에서 관리되며, common
모듈에서는 인터페이스를 통해 접근할 수 있다.
// auth 모듈에서 구현
public class UserDetailsProviderImpl implements UserDetailsProvider {
...
}
✅ 의존성 방향 정리
DIP를 적용하면 모듈 간 의존성 방향이 다음과 같이 정리된다.
- common 모듈에서는 인터페이스(
UserDetailsProvider
)만 포함한다. - auth 모듈에서는 해당 인터페이스를 구현하는 클래스(
UserDetailsProviderImpl
)를 포함하고,common
모듈을 의존하도록 한다.
즉, common 모듈은 구체적인 구현체를 알 필요 없이, 인터페이스를 통해 필요한 기능을 호출할 수 있도록 설계된다. 이렇게 하면 auth
모듈에서 구현체를 변경하더라도 common
모듈에는 영향을 주지 않으며, 유지보수성이 향상된다.
✅ Spring을 활용한 의존성 주입
Spring 프레임워크에서는 이러한 구조를 쉽게 활용할 수 있도록 의존성 주입(Dependency Injection, DI) 기능을 제공한다. @Component
또는 @Service
등의 애너테이션을 사용하여 auth
모듈에서 구현체를 Bean으로 등록하면, common
모듈에서 이를 주입받아 사용할 수 있다.
@Service
public class UserDetailsProviderImpl implements UserDetailsProvider {
@Override
public UserDetails loadUserByUsername(String username) {
// 데이터베이스에서 사용자 정보 조회
}
}
이렇게 하면 Spring이 UserDetailsProvider
의 구현체를 자동으로 주입하여 사용할 수 있도록 설정할 수 있다.
정리: 모듈 설계의 중요성
이번 문제는 모듈을 역할과 기능에 따라 적절히 분리하지 못한 것에서 발생했다. 이를 해결하면서 DIP(의존성 역전 원칙) 의 중요성을 다시 한번 깨닫게 되었다. 모듈을 설계할 때는 다음과 같은 원칙을 고려해야 한다.
- 모듈 간 결합도를 낮추기
- 인터페이스를 활용하여 특정 구현체가 아닌 추상적인 개념에 의존하도록 설계해야 한다.
- 책임 분리를 철저히 하기
- 각 모듈이 담당하는 역할을 명확히 구분하고, 불필요한 의존성을 만들지 않도록 해야 한다.
- 확장성과 유지보수성을 고려하기
- 특정 모듈의 변경이 다른 모듈에 영향을 주지 않도록, 인터페이스 기반의 설계를 적용해야 한다.
✅ 앞으로의 개선 방향
이번 경험을 통해 멀티모듈 프로젝트에서는 초기 설계 단계에서부터 역할과 기능을 명확히 구분하는 것이 중요하다는 점을 다시 한번 확인했다. 앞으로는 설계 단계에서 의존성 방향을 더 철저히 검토하고, DIP와 같은 설계 원칙을 적극적으로 적용할 계획이다. 구조적인 설계를 더욱 고민하며 프로젝트를 발전시켜 나가야겠다.
'Projects > Resolve Problems' 카테고리의 다른 글
MySQL 컨테이너 한글 깨짐 현상 해결 (0) | 2024.12.03 |
---|---|
UnSupportedException (0) | 2024.09.06 |
Uncaught Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. (0) | 2024.07.05 |
CommunicationsException: Communications link failure (0) | 2024.06.16 |
FileNotFoundException (0) | 2024.05.17 |