멀티 모듈을 사용하는 이유
프로젝트가 커지면 여러 개의 서버를 만들어야 할 수도 있다.
만약 Web 서버 하나만 만들다가 Batch 서버를 추가해야 하는 상황이 오고 Web 서버에 있는 User 엔티티를 Batch 서버에도 사용해야 한다면 어떻게 해야할까?
단순하게 User 클래스 파일들을 Batch 서버에 복사하여 붙여넣고 사용할 수도 있지만 이렇게 한다면 User 파일의 코드가 수정되는 경우 각 서버에 있는 모든 User 클래스 파일을 찾아서 각각 코드를 수정해야 한다.
이런 문제점을 해결하기 위한 방법이 멀티 모듈 프로젝트이다.
멀티 모듈은 하나의 공통 프로젝트를 두고 이 프로젝트를 여러 프로젝트에서 가져가서 사용할 수 있도록 기능을 제공한다.
위에서 예시를 들었던 프로그램이 아래 그림과 같다.
Web 서버와 Batch 서버에서 공통으로 사용되는 코드가 User인 것이다.
이들은 각각 모듈이 될 것이고 Web, Batch 모듈에서 공통되는 코드가 User에 있고 각 모듈에서 의존성을 설정하여 사용하는 방식으로 멀티 모듈 프로젝트를 만들 수 있다.
멀티 모듈 프로젝트 시작하기
1. 프로젝트 생성
이렇게 gradle 프로젝트를 생성해도 되고
spring boot 프로젝트를 생성해도 된다.
나는 spring boot 프로젝트를 생성하여 진행했다.
2. 모듈 추가
가장 상위 프로젝트인 example-multi-module 우클릭 → New → Module 클릭
gradle 모듈을 만들어준다.
처음엔 spring 프로젝트를 만들고 지금은 gradle 프로젝트를 만드는 이유는 각 모듈마다 필요한 기능들이 다르기 때문에 gradle 프로젝트를 생성 후 필요한 기능들을 추가해준다.
처음에 spring 프로젝트를 만들면서 Web 설정, lombok 등 공통으로 필요한 것들을 추가해서 생성하였다.
나는 공통 모듈을 API 관련 모듈에서 사용하는 것까지 테스트 하기위해 위와 같이 2개의 모듈을 생성하였다. batch 모듈까지 만들어도 api와 테스트하는 방식은 같아서 생략하였다.
module-api : API 관련 모듈
module-core: 다른 서버 모듈에서 사용하는 공통 모듈 → 엔티티 관련 코드 등
여기서 루트 프로젝트는 하위 모듈을 관리하는 역할만 하기 때문에 루트 프로젝트의 src 폴더는 삭제해도 된다.
이후 settings.gradle에 새로 만들어진 모듈에 대한 코드를 추가해야 하지만 New → Module로 모듈을 생성한 경우에는 인텔리제이가 자동으로 생성해준다.
현재 루트 프로젝트가 하위 모듈로 어떤 프로젝트를 관리하는지 명시해주는 것이다.
위의 코드는 example-multi-module 프로젝트가 module-core, module-api 프로젝트를 하위 프로젝트로 관리한다는 의미이다.
3. 프로젝트 세팅
build.gradle(루트 프로젝트)
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.2'
id 'io.spring.dependency-management' version '1.1.0'
}
repositories {
mavenCentral()
}
bootJar.enabled = false
subprojects {
group = 'com.board'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
//선언한걸 적용하는걸
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
}
- plugins
- 미리 구성해놓은 task들의 그룹으로 특정 빌드과정에 필요한 기본정보를 포함함
- id 'org.springframework.boot' version '3.0.2'
- Spring Boot 종속성을 관리하고 Gradle을 빌드 도구로 사용할 때 애플리케이션을 패키징하고 실행할 수 있다.
- 단독으로 사용되는 경우 프로젝트에 거의 영향을 주지 않는다.
- 예시로 java플러그인과 함께 적용되면 실행 가능한 jar 빌드 작업이 자동으로 구성된다.
- spring-boot-dependencies를 통해서 의존성 관리 기능을 제공하기도 한다.
- repositories
- 각종 의존성(라이브러리)들을 어떤 원격 저장소에서 받을지를 정해준다.
- mavenCentral()이 기본적으로 각종 의존성을 mavenCentral()에서 받아온다는 의미이다
- bootJar.enabled = false
- bootJar 작업은 실행 가능한 jar를 생성하려고 시도하기 떄문에 이를 위해서는 main() 메서드가 필요하다.
- 하지만 루트 프로젝트는 main없이 라이브러리의 역할을 하는 모듈이기 때문에 false로 비활성화 해준다
- subprojects
- settings.gradle에 include된 하위 프로젝트 전부에 대한 공통 사항을 명시한다(루트는 제외)
- subprojects 블록 안에서는 plugins 블록을 사용할 수 없으므로 apply plugin을 사용해야 한다.
module-core
공통적으로 사용하는 코드들을 작성해준다.
테스트만 하기위해 패키지를 만들고 클래스를 생성하여 문자열을 리턴하는 기능을 추가했다.
build.gradle(코어 모듈)
- bootJar, jar
- core 모듈의 경우 main 메서드 없이 라이브러리 역할을 수행하는 모듈이므로 BootJar가 아닌 jar 파일로 생성되고 다른 프로젝트에 첨부될 것이기 떄문에 bootJar = false, jar = true로 설정한다.
module-api
build.gradle(루트 프로젝트)
project(':module-api'){
//bootJar, jar 이 설정이 중요하다고 하셨음
//부팅이 필요한 것, jar파일로 주입하는 것 확인하고 넣는게 중요
bootJar{enabled = true}
jar { enabled = false}
dependencies {
implementation project(':module-core')
implementation 'org.springframework.boot:spring-boot-starter-web'
}
}
루트 프로젝트에 다음과 같이 의존성을 추가해준다.
module-api 의 build.gradle에 작성해도 되지만 이게 관리하기 편해보였다.
- project
- 하위 프로젝트의 의존성을 관리한다
- module-core의 경우 공통 모듈이기 때문에 의존성을 주입해주었다.
- 위와 같이 설정을 해준다음 의존성을 가지고있는지 확인하려면 해당 Gradle → Dependencies → compileClasspath를 확인해주면 된다.
혹시 안보인다면 새로고침 버튼 눌러서 build.gradle 읽게 해주면 된다.
proejct module-core가 있는 것을 볼 수 있다.
사람마다 방식이 다르던데 저렇게 큰 설정은 루트 프로젝트에 하고 세부적인 웹 기능 등의 의존성들은 모듈의 build.gradle에 작성해주는 경우도 많이 보였다. 우선 테스트하기 위해 웹 설정만 있으면 되므로 한번에 다 넣어보았다.
컨트롤러, 서비스 생성
ApiController
package com.board.multimodule.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1")
public class ApiController {
private final ApiService apiService;
public ApiController(ApiService apiService){
this.apiService = apiService;
}
@GetMapping("/test")
public String test(){
return apiService.getText();
}
}
간단하게 서비스를 호출하여 문자열을 리턴하는 컨트롤러다
ApiService
package com.board.multimodule.api;
import com.board.multimodule.test.CoreRepository;
import org.springframework.stereotype.Service;
@Service
public class ApiService {
private final CoreRepository coreRepository;
public ApiService(CoreRepository coreRepository){
this.coreRepository = coreRepository;
}
public String getText(){
return coreRepository.getTest();
}
}
의존성을 추가한 CoreRepository의 getTest()를 반환하는 메소드만 있는 서비스객체이다.
build.gradle에 의존성 추가를 하지 않는다면 CoreRepository를 읽어올 수 없다.
ApiApplication
Application 파일은 패키지에서 가장 먼저 시작해야 한다. 귀찮아서 컨트롤러, 서비스 패키지 만들지 않고 한번에 다 넣었다.
package com.board.multimodule.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.board")
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class,args);
}
}
다른 사람들은 scanBAsePackages 옵션을 주지 않아도 잘 되던데 난 왜 안되는지 모르겠다.
계속 안돼서 왜 안되는지 몰라 에러를 읽어보니
이런 에러가 나왔고 빈 생성이 제대로 되지 않는 것 같아 찾아보니 위처럼 scanBasePackage로 패키지 경로를 읽어주니 잘 되었다.
여기서 또 알게된 주의할 점은 모듈들의 패키지 상위 경로를 맞춰야 된다는 것이다. → com.board와 같이 통일해야 한다. 만약 다르게 한다면 패키지를 여러개 읽어줘야 할 것이다.
안된 이유
@SpringBootApplication 얘가 기본 옵션을 주지 않으면 이 어노테이션이 달린 클래스 파일의 패키지를 기준으로 하위 파일들을 읽어서 빈으로 등록한다.
이전에 안됐던 이유가 multimodule.api 와 core 모듈의 multimodule.test가 일치하지 않아서 coreRepository를 빈으로 등록하지 못한 것이다.
→ @SpringBootApplication 어노테이션이 있는 클래스가 최상단에 있어야 이런 이슈가 생기지 않음
그래서 되도록 패키지 이름을 일치시키고 최상단에 Application 클래스를 만들고 나머지 기능들을 새로운 패키지의 클래스로 넣어주면 이런 이슈는 없을 것이다.
→ @SpringBootApplication 어노테이션이 붙은 파일을 기준으로 해당 파일의 패키지의 하위 패키지들을 읽기 때문이다. api밑에 ApiApplication이 있으니 api 아래의 파일들을 스캔한다.
위에서 scanBasePackages = "com.board"로 하니 문제없이 실행된 이유이다.
여기까지 왔다면 API 테스트를 해서 텍스트가 잘 나오면 끝이다.
API로 접속해도 정상적으로 결과가 나온다.
멀티 모듈 프로젝트 테스트가 잘 되었으니 게시판 만들기에도 적용시켜 진행할 계획이다.
'Java > Spring boot 프로젝트' 카테고리의 다른 글
@ControllerAdvice를 사용한 예외 처리,에러 핸들링 - 게시판 만들기 (6) (0) | 2023.03.16 |
---|---|
Spring 공통 응답 만들기(Enum, 제네릭 타입) - 게시판 만들기(5) (0) | 2023.02.20 |
JPA 레포지터리, Member CRUD - 게시판 만들기(4) (0) | 2023.02.20 |
Spring Boot JPA 설정, 테이블 생성(Entity) - 게시판 만들기(3) (2) | 2023.02.19 |
Spring Boot Mysql 연결, 민감한 정보 관리하기(환경 변수 설정하기)- 게시판 만들기(2) (0) | 2023.02.18 |