일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- Spring Security
- 웹 서버
- mysql
- 데이터베이스
- web server
- 컨테이너
- HTTP
- 스프링 시큐리티
- spring boot
- 도커
- 백엔드
- virtualization
- Java
- CS
- 자바
- 스프링 배치
- 영속성 컨텍스트
- Container
- ORM
- JPA
- 가상화
- computer science
- 배포
- vm
- spring cloud
- Spring
- CI/CD
- 스프링
- 스프링 부트
- spring batch
- Today
- Total
개발 일기
[Spring Boot] 멀티 모듈 (단일) 프로젝트 (Multi-Module Single Project) 모듈화 본문
[Spring Boot] 멀티 모듈 (단일) 프로젝트 (Multi-Module Single Project) 모듈화
개발 일기장 주인 2024. 10. 13. 00:12사실 모놀로틱 단일 모듈 프로젝트에서 어떻게 모듈화를 진행할지 즉, 어떻게 하나의 모듈을 역할과 책임이 명확한 N개의 모듈로 나눌지에 대한 명확한 답을 찾기 위해 여러 래퍼런스를 찾아봤지만 사람들마다 다른 관점을 가져 하나로 모으기 힘들었고 그냥 여러 래퍼런스를 보면서 우아한 기술 블로그가 가장 납득이 갔고 이 글 작성자가 배달의 민족에서 프론트 / 주문 / 결제 시스템 등을 담당해왔고 코덕이란 개인 사이드 프로젝트도 진행 중 이신 개발자 분이기 때문에 신뢰할만하다고 판단했고 해당 게시글 및 발표 영상을 참고하여 정리하면서 다른 래퍼런스의 내용도 추가하면서 이 글을 작성해보겠다.
우선 이 글에서 회원 시스템을 예시로 들었는데 아마 MSA라서 MSA의 여러 서비스 중 하나인 회원 시스템을 잡고 설명을 하신 것 같다. 그래서 당장 나는 MSA가 목표가 아니라 멀티 모듈 단일 프로젝트라서 멤버 서비스를 그냥 하나의 전체 서비스라고 생각하고 글을 읽었다.
단일 모듈 멀티 프로젝트

- member-internal-api
- member-external-api
- member-batch
처음에는 위와 같이 3개의 프로젝트로 구성된 단일 모듈 멀티 프로젝트였다. 그래서 각각의 프로젝트에는 member domain과 같이 공통(중복)되는 로직이 존재했다. 다 복붙으로 진행했고 오타나 잘못 복붙을 했을때 치명적인 오류로도 이어졌다고 한다.
➜ 그렇기 때문에 이는 결과적으로 일관성이 사람에 의존적이였다. 즉, 시스템의 중심 Domain 이 가져야할 구조와 규칙 등을 동일하게 보장해주는 메커니즘 이 없다는 것이 문제였다.
단일 모듈 멀티 프로젝트 + 내부 Maven Repository(Nexus)


그래서 사내 Maven Repository인 Nexus를 파서 거기에 각각의 프로젝트 마다 공통적으로 묶여서 불안정한 상태로 일관성이 유지되던 DTO나 Domain 등을 하나의 프로젝트로 묶어와서 Nexus에 올려 시스템으로 보장되는 일관성을 얻을 수 있었다.("아! 멀티 모듈 프로젝트는 공통으로 사용하는 코드들을 모아놓고 같이 쓸 수 있게 해주는구나!") 그러나 하나를 수정하면 수정한 것을 Nexus에 배포하고 나머지에서 또 이 수정한 프로젝트를 또 다시 받아와야하는 등 번거로운 개발 사이클이 문제였다.
멀티 모듈 단일 프로젝트(오용)
그래서 멀티 모듈 단일 프로젝트가 도입됐고 이는 시스템으로 보장되는 일관성 + 빠른 개발 사이클 이라는 장점을 얻을 수 있었다.
이때 DB와 매핑되는 Domain 이외에 또 공통적으로 각 모듈마다 반복되는 코드를 common 모듈로 분리했다.

공통 모듈에 대부분의 핵심 또는 공통 코드들이 다 들어가게 되었고 이로 인해 공통 모듈의 저주가 시작됐다.
A 어플리케이션 에서 기능을 추가하고 이 때 코드는 고민 끝에 코드는 어떠한 선택에 의해 빈번하든 아니든 공통 모듈에 점점 추가된다. B 어플리케이션 에서도 마찬가지이다. C 어플리케이션 에서 기능을 추가공통 모듈에 작성된 유용해보이는 코드들이 있고 그것들을 사용하게 된다. 사실 그 기능은 A 어플리케이션 을 위해 작성한 코드인 것이다.
위 과정이 반복이 되다보면 어느세 공통 모듈은 걷잡을 수 없이 커져있을 것이다. 그리고 어플리케이션에서 하는 일이 점점 줄어들고 공통 모듈에서 점점 더 많은 일을 하게 된다. 이는 사실 멀티 모듈로 모듈화한 이유가 없어진다.
결국,
- 스파게티 코드: 모든 프로젝트는 주기적 청소가 필요하다. F/O(특정 기능이나 코드가 더 이상 사용되지 않거나, 단계적으로 사용이 중단되는 상황)되거나 리팩토링이 필요해서 코드를 손보려는데 그 범위를 보면 시스템 전체가 되는 것이다.
- 의존성 덩어리: 그만큼 로직들이 하나의 모듈로 모이기 때문에 의존성이 쌓일 수 밖에 없다.
- 공통 설정 문제: 공통 모듈에 설정을 집중하는 경우, 고정적인 호스트 정보는 공통 설정으로 관리할 수 있지만, Thread Pool, Connection Pool, Timeout 등의 중요한 설정까지 포함하는 것은 문제가 된다. 특히, 데이터베이스 커넥션처럼 제한된 자원을 공유하게 되면, 데이터베이스를 사용하지 않는 애플리케이션도 불필요하게 커넥션을 점유할 수 있어 실제로 필요한 애플리케이션에 문제가 발생할 수 있다.



이러한 문제들을 해결할 필요가 있었다.
멀티 모듈 구성하기(올바르게 적용하기)
처음에 주의사항에 대해서 다루는데 내가 고려하면 좋을 것 같은 것들을 정리해보면 애플리케이션 비즈니스와 도메인 비즈니스의 구분이다.
애플리케이션 비즈니스와 도메인 비즈니스의 구분
주문 요청 API가 있다고 했을때 이 둘을 구분하는게 중요하다.


레이어를 구상할때 사용한 규칙

위 규칙을 통해 아래 정의된 레이어를 하나 씩 뜯어보겠다.

1. 내부 모듈 - in system available
- 시스템 안에서 의미를 갖는다. 아마 MSA환경에서 하나의 시스템 내에서 다른 서비스와의 통신을 위한 모듈인 것 같다.
- 어플리케이션, 도메인의 비즈니스를 모른다.



- ex) 배민에서 가게 노출(가게 리스트 및 상세 정보)이라는 프론트 서버를 담당하는 팀에서 프론트 서버는 시스템 내의 다른 다양한 서버들과 통신이 필요했다. 이때 다양한 어플리케이션 모듈과 A라는 서버와 통신을 하게 되면 스펙 중복(코드의 중복), 외부 시스템 변경에 대한 영향 파악 어려움과 같은 문제점이 발생했다.


➜ 그래서 외부 통신을 담당하는 모듈을 위해 해당 모듈이 쓰일 수 있으며 만약 coduck이라는 서버와 통신하는 경우 오른쪽 사진과 같은 역할들을 처리하는 내부 모듈을 생성할 수 있을 것이다.

실제로 어떤식으로 파일을 구성했는지 우아한 테크 세미나 40:47부터 보면 좋을 것 같다.
2. 도메인 모듈 - system domain
두번째로 도메인 모듈이다.
- 어플리케이션 비즈니스를 모른다.
- 하나의 모듈은 최대 하나의 인프라스트럭처에 대한 책임만 갖는다.
- 도메인 모듈을 조합한 더 큰 단위의 도메인 모듈이 있을 수 있다.


- 이런식으로 하게되면 rds가 아닌 다른 db를 끼워 넣는 것이 안되고 새로운 모듈을 생성해야하기 때문에 객체 지향이 깨질 순 있지만 객체 지향을 100% 지켜야한다고 물어본다면 "순수성을 위해 실용성을 포기하는 것은 어리석은 일이다."라고 했다. 그렇기 때문에 만약 infrastructures가 바뀔일이 없다고 생각든다면 이런식으로 처리하는 것도 나쁜 것이라고는 볼 수 없을 것 같다.
- 다중 인프라스트럭처
: 프로비저닝에 단점이 있는 DynamoDB를 사용할때 이 단점을 보완하기 위해 cache로 사용하기 위한 Redis도 함께 도입했을때 다중 인프라스트럭처가 되는데 하나의 모듈안에 이 두가지 DB를 한번에 넣고 처리하면 된다고 생각될 수도 있다.
그러한 경우 시스템 내 admin과 같은 애플리케이션 모듈에서는 DynamoDB만 필요할 수도 있는데 Redis에 대한 의존성까지 딸려오게된다면 모듈화에서 벗어나는 현상이라고 판단된다.


➜ 따라서 domain-redis와 domain-dynamo를 분리하고 Domain Service Module이라는 모듈을 또 따로 만들어서 이 둘 각각의 인프라 설정은 하지않고 캐시 기능(서비스)를 구현하는 모듈을 따로 두었다. 그렇게되면 애플리케이션 모듈들에서 각각이 필요한 모듈을 각자 의존하면 된다.

그래서 Domain Module에는 뭘 담으면 되나

- 첫 번째는 Domain이다. Java 클래스로 표현된 도메인 클래스들이 이곳에 위치한다. JPA를 기준으로 한다면 테이블과 매핑되는 클래스들이다. 도메인 모듈 내부에서는 이곳에 위치한 도메인들을 사용하여 대화를 한다.
- 두 번째는 Repository이다. 도메인의 조회, 저장, 수정, 삭제 역할을 한다. 하지만 모든 조회, 저장, 수정, 삭제 역할을 이곳에서 하는 것은 아니다. 이 모듈은 시스템에서 가장 보호받아야 하며 가장 견고해야 할 모듈이므로, 이 모듈에서 조회, 저장, 수정, 삭제에 대한 정의를 작성할 때 많은 고민을 해야 한다. 예를 들어 Admin Application에서 시스템 도메인에 대한 통계 기능을 추가한다고 했을 때, 통계 조회에 대한 정의는 시스템이 갖는 중심 역할에 따라 달라진다. 만약 통계 기능이 시스템에서 중심 역할을 한다면 도메인 모듈에 작성할 것이고, 그렇지 않다면 사용을 하는 측에서 작성되어야 한다. 만일 이 시스템이 주문이라는 중심 도메인을 갖는다면, 통계 기능은 사용하는 측(Application Module)에 작성할 것이다.
- 세 번째는 Domain Service이다. 이 계층은 도메인의 비즈니스를 책임진다. 도메인이 갖는 비즈니스가 단순하다면 이 계층은 생기지 않을 수도 있다. 이 계층에서는 ‘트랜잭션의 단위’를 정의하고, ‘요청 데이터를 검증’하며, ‘이벤트를 발생’하는 등의 도메인의 비즈니스를 작성한다.
3. 독립 모듈 계층 - independently available
- 시스템과 관련 없이 자체로서 독립적인 역할을 갖는다.
- 당장은 쓰지 않을 것 같아 pass
- spring-boot-custom-yaml-importer
4. 공통 모듈 계층 - system core
- Type, Util 등을 정의한다.

- 또한 순수한 자바만 사용(순수 Java Class 만 정의)할 수 있도록 의존성을 모두 뺏지만 실용성을 위해서라면 추가를 고려하는 것도 틀린 것은 아니다.
- 가능하면 사용하지 않는 것을 원칙으로 두기
bootJar
jar
dependencies {
}

5. 어플리케이션 모듈 - application
- 그동안 짜왔던 모듈을 종합적으로 사용하는 곳



- 즉, 위에 1~4번에서 각각 구성했던 모듈들을 각 api 모듈에서 끌어다 쓰는 것이다.
- 이때 주의할 점은 최소한의 의존성만 끌어다 써야하는 것 같다.

- 각 모듈이 갖는 책임과 역할이 명확하여 리팩토링, 기능의 변경의 영향 범위를 파악하기 용이
- 경계가 명확해짐으로써 기능의 제공 정도를 예측 가능하여 스파게티 코드 발생 가능성 저하
- 역할과 책임에 대한 애매함이 없어짐으로써 어떤 모듈에서 어느정도가지를 개발되어야할지 명확하다.
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot] Spring 외부 API 호출 - RestTemplate과 WebClient (1) | 2024.12.09 |
---|---|
[Spring Boot] JWT를 활용한 인증(Authentication) 및 인가(Authorization) 처리 과정 (2) | 2024.10.20 |
[Spring Boot] 멀티 모듈 (단일) 프로젝트 (Multi-Module Single Project): 역할과 책임이 명확한 모듈 설계 (1) | 2024.10.10 |
[Spring Boot] @ResponseBody와 ResponseEntity 비교 (2) | 2024.09.19 |
[Spring Batch] Spring Batch에서 Tasklet 방식과 Chunk 방식 비교 (0) | 2024.09.19 |