서론
지난 프로젝트에서 도메인 설계와 아키텍처 설계 모두 실패를 겪어 리팩토링을 진행하고 있습니다. 흐름을 제대로 알지 못한 채 헥사고날과 같은 디자인 패턴에만 현혹되어 의미를 알 수 없는 프로젝트가 되어버려 이를 처음부터 리팩토링하고자 시작된 프로젝트입니다.
그런데 왜 갑자기 멀티 모듈이 되었냐? 이번에도 디자인 패턴에 현혹된 것이 아닙니다. CQRS 패턴을 적용하고자 시작부터 MSA로 잘게 쪼개어 복잡성만 증가하고 의미가 퇴색되었던 프로젝트를 모놀리식 멀티 모듈로 재구성하여 점진적으로 확장하는 이야기를 적고자 포스팅을 시작했습니다.
아직 해보지 않아 멀티 모듈이 정답인지 아닌지는 알 수 없습니다. 또한, 모듈을 분리하고 제약하는 데 있어 부족한 점이 많을 수 있습니다. 지나가다가 읽어보신다면 부족한 점을 거침없이 알려주시면 도움이 될 것 같습니다!
멀티 모듈
멀티 모듈이란?
모듈이란 하나의 독립적인 기능을 수행하는 코드 단위를 의미해. 쉽게 말하면, "프로젝트를 기능별로 쪼갠 하나의 단위" 라고 볼 수 있다.
Java에서는 패키지의 상위 구조라고도 볼 수 있는데, 모듈을 사용하는 데는 다음과 같은 이유가 있다.
- 독립적인 기능 수행
- 모듈은 특정 기능을 담당하고, 필요 시 다른 모듈과 연결한다.
- 재사용 가능
- 한 번 만든 모듈은 다른 모듈 및 서비스에서 재사용이 가능하다.
- 즉, 코드의 중복을 줄일 수 있다.
- 관리와 유지보수 용이
- 코드가 역할(기능) 별로 분리되므로 유지보수가 쉬워지고, 변경 시 특정 모듈만 변경할 수 있다.
- 빌드 및 배포 단위로 활용 가능
- 특정 모듈만 빌드하거나, 독립적으로 배포가 가능하다.
멀티 모듈은 이러한 모듈을 여러 개로 나누어 관리하는 구조를 말한다.
코드의 중복을 줄이기 위해 기능에 따라 여러 모듈로 나누고 재사용을 하며 유지보수에 용이한 구조로 사용하면 되겠구나!
라고 처음 멀티 모듈을 접했을 때는 생각했지만 여러 레퍼런스를 보며 끔찍한(?) 생각이었다는 것을 알게 되었다.
어떻게 멀티 모듈을 나눌 것인가?
이 질문이 멀티 모듈이 어려운 이유이자, 적고있는 지금까지도 머리가 아픈 이유인 것 같다.
이 밑으로의 글은 다음 레퍼런스를 보고 배우고 느낀점을 작성했다.
Ref: 우아한 기술블로그 - https://techblog.woowahan.com/2637/
멀티모듈 설계 이야기 with Spring, Gradle | 우아한형제들 기술블로그
멀티 모듈 설계 이야기 안녕하세요. 배달의민족 프론트 서버를 개발하고 있는 권용근입니다. 멀티 모듈의 개념을 처음알게 되었을 때부터 현재까지 겪었던 문제점들과 그것을 어떻게 해결해나
techblog.woowahan.com
무작정 공통 로직을 모듈화 하여 재사용 한다면?
멀티 모듈은 앞서 얘기 했다시피 재사용성 증가와 코드 분리 및 유지 보수를 하기 용이한 구조이다.
따라서 공통 로직을 분리하면 매우 좋을 것 같다는 생각이 들었다.
하지만..
external-api에 있는 기능을 위한 코드와 종속성이 일부 공통 로직으로 빠져 common-module로 들어간다.
batch에 있는 기능을 위한 코드와 종속성이 일부 공통 로직으로 빠져 common-module로 들어간다.
internal-api에 있는 기능을 위한 코드와 종속성이 일부 공통 로직으로 빠져 common-module로 들어간다.
이렇게 common-module은 공통 로직이 모이긴 했지만 batch에서는 필요로 하지 않는 external-api의 로직과 internal-api의 로직이 들어가거나, 필요로 하지 않는 종속성들이 마구잡이로 들어가기 시작한다.
거기에, 설정까지 common-module로 몰리게 되는 경우가 발생하여 Thread Pool, Connection Pool, Timeout 등 민감하고 중요한 부분들이 몰리게 된다.
- 이에 따른 대표적인 문제가 데이터베이스 커넥션의 문제인데, 모든 데이터베이스는 최대 커넥션 개수가 있다. common-module은 데이터베이스를 사용하지 않는 어플리케이션에게 까지 커넥션을 사용하게 만들어 커넥션이 뺏기는 상황이 발생할 수 있다.
이에 따라, common-module에서 코드 리팩토링을 진행할 시 영향 범위는 서비스 전체로 퍼지게 된다.
재사용성과 코드 분리를 통해 유지보수와 독립적인 배포가 가능했던 장점이 common-module이 커지면 커질수록 사라져가고 오히려 단일 모듈 프로젝트 보다 복잡한 스파게티 코드가 되어가는 것을 짐작할 수 있다.
우아한 멀티 모듈 구성하기
멀티 모듈 프로젝트는 실행 가능한 어플리케이션 모듈이 1개 이상, 사용하는 인프라 역시 1개 이상이므로 위와 같은 문제를 피하기 위해
SRP(단일 책임 원칙)와 OCP(개방 폐쇄 원칙)를 철저히 해야한다.
따라서 모듈을 정의하는데 있어 원칙을 정해야 하는데 다음 3가지 원칙을 준수하여 모듈을 추출한다.
- 모듈은 독립적인 의미를 갖는다.
- 모듈은 어떠한 추상화 정도에 대한 계층을 갖는다.
- 계층 간 의존 관계에 대한 규칙이 있다.
이에 따라 시스템에서 가져야 할 구조를 정의한다.
- 어플리케이션 모듈 계층
- 내부 모듈 계층
- 도메인 모듈 계층
- 공통 모듈 계층
1. 어플리케이션 모듈 계층
독립적으로 실행 가능한 계층으로 하위 설계 했던 모듈들을 조립하여 비즈니스 서비스를 완성시킨다.
어플리케이션 모듈 | 내부 모듈 | 도메인 모듈 | 공통 모듈 | |
사용 가능한 모듈 여부 | X | O | O | O |
해당 계층의 모듈로는 주로
- batch
- worker
- internal-api
- external-api
모듈들이 배치될 수 있다.
해당 계층은 하위 모듈들을 조립하는 모듈이기 때문에 사용성에 따라 의존성을 추가하여 사용한다.
2. 내부 모듈 계층
저장소, 도메인 외 시스템에서 필요한 기능을 모은 모듈
이 계층은 다음과 같은 원칙을 반드시 준수한다.
- 어플리케이션, 도메인 비즈니스를 모른다.
시스템 전체적인 기능을 서포트 하기 위한 기능 모듈이므로 어떠한 실행 가능한 어플리케이션에서도 독립적으로 사용이 가능해야 한다.
따라서 이 모듈은 모데인 계층을 의존하지 않는다.
어플리케이션 모듈 | 내부 모듈 | 도메인 모듈 | 공통 모듈 | |
사용 가능한 모듈 여부 | X | O | X | O |
해당 계층의 모듈로는 주로
- web
- web filter를 통한 보안, 로깅 등 웹에 대한 필수적인 공통 설정
- client
- 외부 시스템과 통신을 책임지는 모듈, 비즈니스와 관계없이 요청과 응답을 할 수 있는 사용성 제공
- event-publisher
- Spring Application Event에 대한 처리 담당. SQS로 이벤트 전송 및 로그를 남기는 행위도 처리
모듈들이 배치될 수 있다.
3. 도메인 모듈 계층
저장소와 밀접한 중심 도메인을 다루는 계층. 견고하고 특별하게 격리되어 관리해야함
이 계층은 아래와 같은 원칙을 갖는다.
- 서비스 비즈니스를 모른다.
- 하나의 모듈은 최대 하나의 인프라 스트럭처에 대한 책임만 갖는다.
- 도메인 모듈을 조합한 더 큰 단위의 도메인 모듈이 있을 수 있다.
도메인 계층은 어플리케이션 계층에서도 사용 가능한 모듈이므로, 저장소 외 시스템 특성을 알지 알아야 한다.
따라서 내부 모듈 계층은 의존하지 않는다.
어플리케이션 모듈 | 내부 모듈 | 도메인 모듈 | 공통 모듈 | |
사용 가능한 모듈 여부 | X | X | O | O |
단일 인프라스트럭처 사용 모듈
RDBMS가 중심 도메인을 품는 프로젝트는 대개 이런 경우로
도메인 모듈은 다음과 같은 책임을 갖는다.
- Domin : Java Class로 표현된 도메인 Class들이 위치. JPA 기준으로는 테이블과 매핑되는 Class
- Repository : 도메인의 CRUD 역할 수행. 여기서 중요한 점은 모든 CRUD를 전부 이곳에서 하는 것이 아님. 시스템에서 해당 기능이 중심 역할로 볼 수 있다면 도메인 모듈에 CRUD가 작성될 것이고, 그렇지 않다면 사용되는 측(Application Module)에 작성되어야 한다.
- Domain Service : 도메인의 비즈니스를 책임진다. 트랜잭션의 단위를 정의하고, 요청 데이터 검증 및 이벤트 발급 등의 역할 수행
다중 인프라스트럭처 사용 모듈
도메인 계층에서 여러 인프라스트럭처를 사용하면 의존성이 꼬이는 경우가 종종 발생한다.
RDBMS를 사용하는 도메인 모듈이 임시 저장 혹은 조회성 개선을 위해 Redis 의존성을 추가한다면 의존성이 뒤죽박죽이 되는 경우가 발생할 수 있다.
이를 해결하기 위해 모듈과 인프라스트럭처는 1:1로 작성하는 것을 추천하고, 두 인프라스트럭처 사이의 관계가 생기면 두 모듈을 품는 모듈을 작성하거나 두 인프라스트럭처 모듈을 품는 어플리케이션에서 처리하는 것이 좋다.
4. 공통 모듈 계층
공통 모듈을 분명 없애라고 했을텐데 이게 왜있지 라는 생각이 들 수 있다. (필자도 들었다.)
하지만 공통 로직은 필연적으로 나올 수 밖에 없고, 이 모듈에는 큰 제약사항을 두어 사용한다.
- Type, Util 등을 정의한다.
- 가능하면 사용하지 않는다.
- 어떠한 외부 의존성을 가져오지 않는다.
시스템 내 모든 모듈들이 의존할 수 있어야 하기 때문에 얇은 의존성을 제공하기 위해 어떠한 모듈도 의존하지 않는다.
어플리케이션 모듈 | 내부 모듈 | 도메인 모듈 | 공통 모듈 | |
사용 가능한 모듈 여부 | X | X | X | X |
따라서 공통 모듈은 순수 Java Class 만 정의할 수 있으며, 코드 중복이 자주 일어나는 Type 성격의 DTO나 기본적인 Util Class 만 배치가 가능하다.
멀티 모듈 기대 효과
이렇게 힘들게 나눴으면 그만큼의 효과가 있어야 할 것이다.
- 명확한 추상화 경계
- 계층에 따른 모듈 분리로 추상화 레벨을 맞출 수 있음
- 이로 인한 개발의 생산성 향상
- 리팩토링, 기능의 영향 범위 파악 용이
- 기능의 제공 정도 예측 가능
- 역할과 책임에 대한 애매함 해소
- 최소 의존성
- 의존성이 많으면 생기는 일들
- 여지의 개방으로 개발 생산성 하락 -> 스파게티 의존으로 생산성 저하
- 예상치 못한 Spring Boot의 설정이 로드 되는 일 방지
- 의존성이 많으면 생기는 일들
이번 글은 경험 보다는 개념 학습과 정의를 위주로 포스팅 하였고, 다음 포스트 부터는 어떻게 프로젝트에 적용할지에 대한 고민을 해볼 예정입니다.
'Tech > Develop' 카테고리의 다른 글
MSA 환경 CQRS 패턴 적용기 - (2) 프로젝트에 적용해보기 (0) | 2024.08.07 |
---|---|
MSA 환경 CQRS 패턴 적용기 - (1) CQRS (0) | 2024.08.04 |