-
Spring) 아키텍처(1) 모듈, 멀티모듈Spring 2024. 11. 2. 16:23
개요
회사의 여러 프로젝트를 통해 다양한 개발 방식을 접해보았지만, 현재 진행 중인 서비스의 프로젝트는 체계적인 아키텍처가 부족하고, 일관된 개발 철학도 마련되어 있지 않은 상태입니다. 특히 MSA(Microservices Architecture)를 지향하고 있음에도 불구하고, 진정한 MSA의 장점을 살리지 못한 채 일관성과 원칙이 부족한 방식으로 운영되고 있어 개선이 필요하다고 느끼고 있습니다.
이번 기회를 통해 현재 서비스의 아키텍처 상태를 철저히 분석하고, 앞으로 나아가야 할 아키텍처의 방향을 명확히 설정하려 합니다. 이를 위해 먼저 아키텍처와 설계 원칙에 대해 심도 있게 공부하고, 이를 바탕으로 현 프로젝트에 필요한 설계 방향과 개선 방안을 정립하려고 합니다. 이러한 학습과 분석 과정을 통해 서비스의 구조를 최적화하고, 향후 유지보수성과 확장성을 고려한 탄탄한 아키텍처 기반을 마련하고자 합니다.
JDK 모듈 시스템 (Java Platform Module System, JPMS)
JDK 의 모듈은 제가 쓴 예전 가비지 컬렉션의 글(https://will-of-rough.tistory.com/53)에서 처럼 JDK9버전부터 도입이 되었습니다.설명
- 목적: JDK 모듈 시스템은 주로 JDK와 애플리케이션 자체를 모듈화하여 성능을 최적화하고 불필요한 의존성을 줄이는 것을 목적으로 합니다. 모듈 간의 관계를 명시하여 런타임이나 컴파일 타임에 모듈 간 종속성 문제를 사전에 방지할 수 있습니다.
- 구조: 모듈 시스템에서는 module-info.java 파일을 통해 모듈의 의존성을 명시합니다. 예를 들어, 각 모듈은 자신이 필요한 모듈을 requires 키워드로, 외부에 노출할 패키지는 exports 키워드로 정의합니다.
- 특징: 모듈 간의 접근을 강력하게 제한하고, 특정 패키지에 대한 의존성만을 명확하게 정의할 수 있어 애플리케이션 구조를 더 명확하게 만들어 줍니다.
의존성 문제와 성능 저하 예시
1. 불필요한 의존성 로딩
- 예: 애플리케이션이 간단한 수학 계산만 수행하는데, 데이터베이스 관련 라이브러리까지 포함되어 메모리에 로드됩니다.
- 문제: 애플리케이션이 데이터베이스와는 무관하지만, 데이터베이스 라이브러리가 로드되어 메모리 낭비가 발생합니다.
2. 성능 저하
- 예: 간단한 API 서버가 대용량 이미지 처리와 대규모 데이터 분석 라이브러리를 포함하고 있어 전체 프로젝트의 용량과 메모리 사용량이 커집니다.
- 문제: 기본적인 API 요청만 처리해도, 사용하지 않는 무거운 라이브러리가 함께 로드되어 메모리 사용량이 증가하고 애플리케이션 성능이 저하됩니다.
3. 의존성 관리 어려움
- 예: 프로젝트 내에 수학 연산, 파일 처리, 네트워크 통신 등 다양한 기능이 서로 얽혀 있고 모든 코드가 한 프로젝트에 포함되어 있습니다.
- 문제: 프로젝트가 커지면서 어느 클래스가 어떤 기능에 의존하는지 파악하기 어려워지고, 특정 기능을 수정할 때 다른 기능에 영향을 미칠 수 있어 유지보수가 어렵습니다.
이런 문제들을 JDK 모듈 시스템을 통해 필요한 기능별로 모듈화하면 불필요한 라이브러리 로딩을 줄이고, 성능을 최적화하며, 의존성 관계를 명확하게 관리할 수 있습니다.
모듈시스템 예시
JDK 모듈 시스템의 예제를 통해 기본적인 개념과 사용법을 알아보겠습니다. 예를 들어, 두 개의 모듈을 만들어 하나의 모듈에서 다른 모듈의 기능을 사용하는 예제를 살펴보겠습니다.
1. 프로젝트 구조 설정
우리는 두 개의 모듈, com.example.app과 com.example.util을 만든다고 가정합니다. com.example.util 모듈에는 유틸리티 클래스가 있고, com.example.app 모듈이 이를 사용하는 구조입니다.
project-root/ ├── com.example.app/ │ ├── module-info.java │ └── com/example/app/MainApp.java └── com.example.util/ ├── module-info.java └── com/example/util/StringUtils.java
2. 모듈 정의 파일 설정
각 모듈의 최상위 디렉터리에 module-info.java 파일을 작성합니다. 이 파일은 모듈 시스템에서 각 모듈의 의존성과 공개할 패키지를 정의합니다.
com.example.util/module-info.java
module com.example.util { exports com.example.util; // 다른 모듈에서 사용할 수 있도록 패키지를 공개 }
com.example.app/module-info.java
module com.example.app { requires com.example.util; // com.example.util 모듈에 대한 의존성을 명시 }
module-info.java 파일을 통해 com.example.util 모듈은 자신의 com.example.util 패키지를 외부에 공개하며, com.example.app 모듈은 com.example.util 모듈에 대한 의존성을 선언했습니다.
3. 유틸리티 클래스 작성
유틸리티 기능을 제공하는 StringUtils 클래스를 com.example.util 모듈에 작성합니다.
com.example.util/com/example/util/StringUtils.java
package com.example.util; public class StringUtils { public static String toUpperCase(String input) { return input == null ? null : input.toUpperCase(); } }
4. 메인 클래스 작성
이제 StringUtils 클래스를 사용하는 메인 클래스를 com.example.app 모듈에 작성합니다.
com.example.app/com/example/app/MainApp.java
package com.example.app; import com.example.util.StringUtils; //위에서 생성한 StringUtils 를 import 할 수 있다. public class MainApp { public static void main(String[] args) { String original = "hello world"; String uppercased = StringUtils.toUpperCase(original); System.out.println("Original: " + original); System.out.println("Uppercased: " + uppercased); } }
설명
- module-info.java 파일을 통해 각 모듈의 의존성과 공개할 패키지를 명시합니다.
- requires 키워드를 통해 모듈 간 의존성을 설정하며, exports 키워드로 패키지를 외부에 공개할 수 있습니다.
- StringUtils 클래스는 com.example.util 모듈의 com.example.util 패키지에 정의되어 있으며, 이를 com.example.app 모듈에서 사용했습니다.
이 예제는 JDK 모듈 시스템이 모듈 간 의존성을 명확히 정의하여 코드 구조를 개선하고 캡슐화를 강화할 수 있음을 보여줍니다.
Spring Boot 멀티모듈 시스템
Intellij 의 멀티모듈 Spring Boot의 멀티모듈 프로젝트는 애플리케이션을 논리적으로 여러 개의 모듈로 분리하여 각 모듈의 역할을 명확히 하고 재사용성을 높이며 개발 및 유지보수의 효율성을 증가시키는 방법입니다. 멀티모듈 구조는 특히 규모가 큰 애플리케이션이나 팀 프로젝트에서 각 기능을 독립적으로 개발하고 테스트할 수 있도록 돕습니다.
- 목적: Spring Boot 멀티모듈 시스템은 주로 비즈니스 로직, 데이터 액세스, 서비스, API 등으로 애플리케이션을 논리적으로 나누어 코드 관리의 효율성을 높이고 재사용성을 강화하는 데 중점을 둡니다.
- 구조: Spring Boot 멀티모듈 시스템에서는 프로젝트를 여러 하위 모듈로 나누고 각 모듈이 독립적인 빌드 단위로 관리됩니다. 예를 들어, Maven이나 Gradle을 사용해 각 모듈을 구성하며, 모듈 간 의존성을 선언하여 필요 시 종속성을 공유하거나 참조할 수 있습니다.
- 특징: Spring Boot는 JDK 모듈 시스템을 필수로 사용하지 않으며, 대신 Gradle 또는 Maven의 dependencies 블록을 사용하여 모듈 간 의존성을 설정합니다. 모듈화가 잘 이루어지면, 특정 기능을 독립적으로 개발하거나 테스트하는 데 도움이 되며, 유지보수가 용이해집니다.
1.Spring Boot 멀티모듈 구조의 기본 개념
출처 : https://techblog.woowahan.com/2637/ Spring Boot 멀티모듈 프로젝트는 일반적으로 루트 프로젝트와 여러 하위 모듈로 구성됩니다. 이때 각 하위 모듈은 독립된 기능을 수행하며, 다른 모듈이 필요하면 의존성으로 추가하여 사용할 수 있습니다.
예를 들어, 쇼핑몰 애플리케이션을 개발한다고 가정할 때 다음과 같은 모듈로 나눌 수 있습니다:
- api: 클라이언트 요청을 처리하는 REST API 모듈
- service: 비즈니스 로직을 포함한 서비스 모듈
- repository: 데이터베이스와 상호작용하는 모듈
- common: 공통적으로 사용되는 유틸리티나 DTO를 모아둔 모듈
이와 같이 구조를 나누면 모듈별로 독립적으로 개발하고 테스트할 수 있으며, 각 기능을 더 명확하게 구분할 수 있습니다.
2. 프로젝트 구조 생성 방법
다음은 Spring Boot 멀티모듈 프로젝트의 구조 생성 방법 입니다.
멀티 모듈은 어느것을 주체로 하느냐에 따라 세팅방법이 달라집니다.
우선 예시의 경우 프로젝트 안에 모듈로 되어있는것마다 서버가 될 예정입니다. 만약에 전체적인것에 대해 모듈을 할 예정이라면 다른방식으로 되어야합니다.
1)프로젝트 생성 (Spring Boot, Groovy 상관없습니다)
Groovy 에서 생성해도 됩니다. 2) 기존 src 파일 제거
3) 모듈 생성
core-module 생성 multi-projects/ ├── main-server/ # Main Server │ ├── src/main/java/ │ │ └── com/main/api/ │ │ └── controller/ # API Controller 클래스 포함 │ └── build.gradle ├── message-server/ # Message Server │ ├── src/main/java/ │ │ └── com/messgage/api/ │ │ └── controller/ # API Controller 클래스 포함 │ └── build.gradle ├── core-module/ # 비즈니스 로직 모듈 │ ├── src/main/java/ │ │ └── com/core/service/ │ │ └── service/ # 비즈니스 로직 서비스 클래스 포함 │ └── build.gradle └── build.gradle # 루트 빌드 파일
3. 루트(최상위) 빌드 파일 설정
루트 파일에서 하위 모듈들을 포함하고, 공통적인 의존성을 정의합니다.
크게 settings.gradle 을 통한 세팅과 build.gradle 을 통한 세팅이 있습니다.
- settings.gradle 은 멀티모듈 프로젝트의 모듈 구성만을 설정하고 모듈 간 포함 관계를 지정하는 역할입니다.
- build.gradle 은 모듈의 의존성을 관리하고, 프로젝트 전반에 걸친 빌드 및 실행 환경을 구성합니다.
1) 루트 settings.gradle
이곳에 본인이 생성한 모듈 이름을 작성한다.
생성한 모듈 이름 (core-module, main-server, messgae-server) rootProject.name = 'multi-projects' include 'core-module', 'main-server', 'message-server'
2) 루트 build.gradle만약에 최상위 빌드를 하려면 루트 build.gradle 에 설정을 해야한다. 하지만 내가 만드려는것은 여러개의 서버가 공통된 core-module 을 사용하기때문에 할 필요가 없다.
plugins { id 'java' id 'org.springframework.boot' version '3.1.0' id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' subprojects { apply plugin: 'java' apply plugin: 'io.spring.dependency-management' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter' } } project(':message-server') { dependencies { implementation project(':core-module') implementation 'org.springframework.boot:spring-boot-starter-web' } } project(':main-server') { dependencies { implementation project(':core-module') implementation 'org.springframework.boot:spring-boot-starter-web' } }
- subprojects 블록: 하위 모든 모듈에 공통 설정을 적용합니다.
- 각 모듈에 필요한 의존성을 implementation을 통해 추가하여 모듈 간 의존성을 설정합니다.
3) 각 위치의 settings.gradle 제거의존성을 최상위 루츠 settings.gradle 이 관리하기에 하위 모듈들의 settings.gradle 이 존재한다면 역의존성이 성립됩니다. 그렇기에 삭제해줄 필요가 있습니다.
4) main-server/build.gradle
dependencies { implementation project(':core-module') implementation 'org.springframework.boot:spring-boot-starter-web' }
4. 빌드 및 실행
프로젝트 루트 디렉터리에서 다음 명령어를 사용해 빌드하고 실행할 수 있습니다.
intelliJ 의 gradle 에서 하위 모듈의 빌드를 사용하면됩니다.
5. 멀티모듈의 장점
- 모듈화된 코드 관리: 각 모듈이 독립적으로 관리되므로 수정이 용이하고, 재사용성이 높아집니다.
- 의존성 관리: 모듈별로 필요한 의존성만을 설정해 불필요한 라이브러리를 로드하지 않아 성능을 최적화할 수 있습니다.
- 유지보수성 향상: 모듈별로 테스트하고 개발할 수 있어 유지보수가 용이하며, 특정 기능을 개선하거나 확장하기에도 적합합니다.
JDK 모듈 시스템과 Spring Boot 멀티모듈의 관계
- 독립적 사용: Spring Boot 멀티모듈 프로젝트는 JDK 모듈 시스템을 필수적으로 사용할 필요가 없으며, 프로젝트에 맞게 선택적으로 사용할 수 있습니다. Spring Boot에서는 JDK 모듈 시스템 대신 전통적인 JAR 파일 의존성을 관리하는 방식으로도 충분히 모듈화가 가능합니다.
- 병행 사용 가능: JDK 모듈 시스템을 도입해 module-info.java 파일을 추가해도, Spring Boot의 멀티모듈 구조를 유지하면서 JPMS를 병행해 사용할 수 있습니다. 단, Spring Boot의 여러 라이브러리들이 JDK 모듈 시스템과 충돌을 일으킬 가능성이 있어 호환성을 면밀히 검토해야 합니다.
- 메모리 및 성능 최적화: JDK 모듈 시스템을 Spring Boot 멀티모듈과 함께 사용하면, JDK와 애플리케이션의 모듈화된 라이브러리를 불러옴으로써 메모리 사용량을 최적화할 가능성이 있습니다. 특히 Spring Boot의 다양한 모듈이 Docker 환경에서 구동될 경우, JPMS를 통해 불필요한 종속성 로딩을 방지할 수 있어 메모리 관리에 도움을 줄 수 있습니다.
결론
JDK의 모듈화와 Spring Boot의 멀티 모듈 구조는 서로 다른 개념이지만, 프로젝트 개발 환경에서 모듈화의 기본 목표는 두 가지 경우 모두 유사합니다. 특히 여러 개발자가 협업할 때, 모듈화는 프로젝트 내의 의존성을 줄이고 각 모듈의 개발 편의성을 높이는 데 중요한 역할을 합니다.
JDK의 모듈화는 주로 자바 언어 수준에서 모듈 단위로 패키지를 구성하고, 의존성 관리를 명시적으로 정의해주는 방식으로, 자바 9부터 도입되었습니다. 이로 인해 자바 애플리케이션은 필요한 모듈만 선택적으로 로드하여 성능을 최적화하고 보안을 강화할 수 있습니다.
반면, Spring Boot의 멀티 모듈은 애플리케이션의 기능 단위를 여러 모듈로 나누어, 각 모듈이 개별적인 역할을 수행하면서도, 전체 프로젝트의 구조를 논리적으로 분리할 수 있도록 돕는 구조입니다. 멀티 모듈 구조에서는 공통 기능을 별도 모듈로 정의하여 각기 다른 서비스들이 이를 재사용할 수 있고, 모듈 간 의존성을 명확히 구분할 수 있습니다. 이를 통해 팀 간 병렬 개발이 용이해지고, 코드 관리와 배포 효율성이 높아집니다.
현재 프로젝트에서는 Spring의 멀티 모듈 구조를 활용하여, 공통 기능을 하나의 프로젝트로 통합하고 분산된 서버들을 한 프로젝트 내에서 관리하려고 합니다. 이러한 접근 방식을 통해 서버 간의 공통된 기능을 모듈화하고, 유지보수와 확장성을 높이면서도 불필요한 코드 중복을 줄일 수 있을 것입니다.
다음 글에서는 프로젝트 아키텍처에 대한 설명과 함께, 현 프로젝트에 멀티 모듈을 어떻게 반영할지에 대해 구체적으로 안내드리겠습니다.
'Spring' 카테고리의 다른 글
Spring) 아키텍처(3) 모노리스 VS MSA (4) 2024.11.08 Spring) 아키텍처(2) 소프트웨어 아키텍처란? (2) 2024.11.04 Java) Checked Exception, Unchecked Exception 그리고 Error (0) 2024.09.01 Spring) @Autowired 와 생성자 주입(Constructor Injection) (0) 2024.08.27 Spring) Bean 의 생명주기(Bean Lifecycle) (0) 2024.08.20