멀티모듈 설계 고민 - (1)

땡글이·2023년 10월 25일
post-thumbnail

혼자 개인프로젝트를 진행하면서 멀티모듈로 서버를 구성하면서 했던 고민들과 기존 모듈 설계는 어떠했는지, 기존 설계에서 어떻게 개선했는지 에 대해 소개해보고자 한다!

물론 개선한 내용도 시간이 지나면 또 마음에 안들겠지만,,, 그래도! 지금은 나름 만족한 설계인 것 같아 이렇게 끄적여본다

기존 모듈 설계 ㅎㅎ..

📁 프로젝트 폴더
├── juju-api
│   └── {controller}
│   └── {dto}
│   └── {facade}
├── juju-domain
│   └── {entity}
│   └── {repository}
│   └── {service}

좋은 모듈 설계란??

모듈별 분리가 잘 된 프로젝트 혹은 모듈 설계가 잘 된 프로젝트라는 것에 대해서는 어떤 사람이 해석하느냐에 따라 다를 것이다.
하지만 모듈 분리가 잘된, 모듈 설계가 잘된 프로젝트에 대해서 가지게 된 내 개인적인 우선순위는 다음과 같다.

  • 직관적이어야 한다
  • 불필요한 의존 관계로 휴먼에러를 방지해야 한다

직관적이어야 한다

너무 당연한 얘기지만, 이 얘기를 풀어 써보자면 "어떤 기능을 수정하고자 할 때 관련 코드가 어느 모듈, 어느 패키지에 있을지 한번에 추측이 되어야 한다" 라는 의미로 적어둔 것이다.

만약, api 모듈과 domain 모듈만 있는 상황에서 로깅 관련된 설정을 바꿔야한다면 어느 모듈부터 찾아볼까?? 처음에는 어떤 모듈에 있는지 모른다면, 각 모듈에 logback.xml 파일이 있는지 LoggerAdvice 혹은 LoggerInterceptor 같은 클래스가 있는지 확인해봐야 한다.

"찾는 건 IDEA로 금방 찾을 수 있는데?"" 라고 생각하실 수도 있다. 물론 IDEA에서 검색 기능활용해서 바로 찾을 수 있긴 하다!!

근데 만약 여러 사람이 작업하는 상황이라고 가정하면, 명수는 api 모듈에 logback.xml 을 만들어두고, 철수는 domain 모듈에 LoggerAdvice 와 같은 설정을 만들어뒀다면?? 이런 부분들이 계속 쌓이게되면 관리 포인트가 분산되어서 관리하기가 어려워질 수 있다!

그래서 내 생각에는 어떤 기능을 추가하거나 수정을 할 때에는 한 번에 "어디에 둘까?" 가 아닌 "여기에 위치시켜야지!" 라는 논리흐름으로 흘러갈 수 있도록 구성되면 좋겠다는 생각이다!

불필요한 의존 관계로 휴먼 에러를 방지해야 한다

기존 모듈 설계를 보면 알겠지만, 너무나 단순한 모듈 구조라서 불필요한 의존관계니 뭐니 할 것도 없긴 하다 ㅎㅎ.. 하지만, 모듈을 개선하기 위해선 모듈별 역할을 분리해주고 직관적인 모듈을 만들어줘야 하는데 그렇게 분리되다보면 여러 모듈 간에 의존성이 발생하기 마련이다.

내가 개선한 모듈 설계는 다음과 같다!

📁 프로젝트 폴더
├── juju-admin
│   └── {controller}
│   └── {interceptor}
│   └── {filter}
├── juju-api
├── juju-core
│   ├── application
│   │   └── {dto}
│   │   └── {servicefacade}
│   └── domain
│   │   └── {domain}
│   │   └── {service}
│   │   └── {repository}
├── juju-infrastructure
│   └── {repositoryImpl}
│   └── {entity}
│   └── {config}
├── juju-support
│   ├── constants
│   │   └── {필요한 상수}
│   └── utils
│   │   └── {필요한 유틸성 클래스}

juju-admin, juju-api 모듈은 컨트롤러 및 필터 등을 담당하는 모듈이고, juju-core:domain 모듈에는 정말 필요한 핵심 로직들(도메인, 서비스, 리포지토리)이 담겨있고, juju-core:application 모듈에는 domain 모듈에서의 서비스들을 참조해서 복잡한 비즈니스 로직을 담당하는 서비스(Facade 패턴)들이다. juju-infrastructure 모듈에서는 repository의 구현체 집합 및 datasource 관련 설정들이 포함되어 있다. juju-support 모듈은 모든 모듈에서 참조해서 사용할 수 있는 유틸성 클래스 및 상수, 외부 라이브러리 관련 설정 등을 모아두는 모듈이다.

의존관계가 어떻게 되어야하겠는가? 난 처음에는 단순히 아래처럼 생각했다

infrastructure < core:domain < core:application < app(admin or api)

근데, 여기서 생각해봐야할 점은, domain 모듈이 다른 모듈(infrastructure)을 바라봐야하는가? 이다.

Domain 모듈은 다른 모듈을 참조해야하는가?

결론은 아니다! 결국, service, repository 모두 domain 모듈에 잡아뒀기 때문에, 해당 모듈에서는 repository에 어떤 구현체가 붙어있든 몰라도 된다.

만약 어떤 구현체가 붙어있는지까지 domain 모듈이 알게되면, DIP 원칙 위반이다!

근데 결국 repository 구현체는 붙여서 빈 주입이 가능해야하는데 어떻게 구현할까?? 내가 한 해결방법은application 모듈에서 runtimeOnlyinfrastructure를 주입해주는 것이다.

왜 runtimeOnly로 참조하는가?

runtimeOnly 는 런타임 시점에만 타겟 모듈을 바라보기 때문에, 개발 중에 타겟 모듈의 인터페이스 혹은 구현체에 접근하지 못하므로, 다른 사람 혹은 내자신이 잘못된 모듈 참조를 하는 상황을 컴파일 시점에 막음으로써 좀 더 확실한 구조가 될 수 있다.

그럼 모듈간 의존성 관계는 아래와 같이 바뀌게 된다


결론

참 많은 고민들을 하면서 설계를 해봤다. 사실 지금 이것도 추후에는 더 좋은 아키텍처가 생각나고 별로 마음에 안들 수 있지만, 현재는 가장 나에게 납득이 되고 합리적인 모듈 설계였던 것 같다. 마음에 안드는 순간이 오면 다시 포스팅할 것 같다 ㅎㅎㅎㅎ 긴 글 읽어주셔서 감사합니다 😊

profile
꾸벅 🙇‍♂️ 매일매일 한발씩 나아가자잇!

2개의 댓글

comment-user-thumbnail
2024년 7월 9일

안녕하세요 글 너무 잘봤습니다.
저도 Domain 모듈과 Repository 를 포함하는 Infra 모듈을 나누는 과정에서, QueryDsl 사용을 위해 테이블을 나타내는 Entity 객체는 Domain 에 위치하는데, QueryDsl 을 사용한 RepositoryImpl 은 Infra 에 두고싶어 많은 고민을 하다가 해당 글을 찾아보게되었습니다.

글을 모듈 구조를 보면 Domain은 Entity 가 아니며, Domain 모듈의 Repository 또한 비지니스로직에 대해 먼저 interface를 정의하고 infraStructure 모듈에서 jpa 나 querdsl 을 이용한 구현체 를 두어 Domain 과 Infra 의 의존성을 단방향으로 연결한게 맞을까요,,.???

맞다면 Domain 과 Entity 의 차이점을 어떻게 두셨는지가 궁금합니다...!
많은 고민에 휩싸인 상태였는데 머리를 망치로 맞은것만 같네요,, 고민에 대한 상세한 공유, 너무 값진 글 감사드립니다.

1개의 답글