본문으로 바로가기
반응형

개요

Spring Boot 3 환경에서 멀티 모듈을 설정하는 방법에 대해 기술한다. 참고로 본 포스팅에서의 환경은 다음과 같다.

Spring Boot: 3.0.2
Java: 17
Gradle

프로젝트 생성

최초 프로젝트를 생성해준다. 나는 위와 같이 설정해줬고 의존성은 Spring Web하나만 추가해줬다.

모듈 생성

프로젝트 최상위 폴더에서 오른쪽을 클릭하고 New - Module을 클릭한다.

Gradle을 설정하고 Java에 체크한 후 Next를 누른다.

원하는 모듈의 이름을 적어준다. 참고로 본 포스팅에서는 외부 요청을 담당하는 모듈인 clients, API서버를 담당하는 app-api 2개의 모듈을 만들 것이다. 

rootProject.name = 'demo'
include 'clients'
include 'app-api'

위 과정을 반복하여 clients, app-api라는 모듈을 만들고 settings.gradle 파일을 열어보면 위처럼 자동으로 우리가 생성한 모듈이 include된 모습을 확인할 수 있다.

최상위 폴더 build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.2'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

최초 생성 당시 build.gradle을 보면 위와 같은 모습이다.

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.2'
	id 'io.spring.dependency-management' version '1.1.0'
}

subprojects {
 	// 추가된 부분
	apply plugin: 'java'
	apply plugin: 'org.springframework.boot'
	apply plugin: 'io.spring.dependency-management'

	group = 'com.example'
	version = '0.0.1-SNAPSHOT'
	sourceCompatibility = '17'

	repositories {
		mavenCentral()
	}

	dependencies {
		implementation 'org.springframework.boot:spring-boot-starter-web'
		testImplementation 'org.springframework.boot:spring-boot-starter-test'
	}

	tasks.named('test') {
		useJUnitPlatform()
	}
}

plugins 아래에 있는 모든 라인을 subprojects 내부로 넣어준다. 그리고 추가된 부분이라고 주석으로 써둔것 처럼 apply plugin을 통해 위에 명시해둔 플러그인을 적용시킨다. 참고로 최상위 build.gradle에 있는 dependencies는 하위 모든 모듈에 적용되기 때문에 공통적으로 사용될 의존성만 명시해준다. 그리고 각 모듈에서 필요한 의존성은 각 모듈 하위에 있는 build.gradle에 추가하도록 한다.

app-api 모듈

현 상태에서 폴더 구조를 보면 위와 같다. 최초 프로젝트 생성 당시 src폴더가 생성되었고 하위에 DemoApplication이라는 파일이 하나 존재한다. 해당 파일이 서버 시작의 진입점이므로 app-api/src/main/java 하위에 src와 동일하게 com/example/demo이라는 폴더를 생성해준다.

참고로 아래에서 clients 모듈도 동일하게 폴더 구조를 가져갈 것인데, 그렇게 하지 않는다면 @Component등으로 설정해둔 빈을 타 모듈에서 참조할 수 없다. 물론 SpringBootApplication의 scanBasePackages 옵션을 통해 해결할 수 있지만 이 방법은 모듈이 추가될 때 마다 해당 옵션을 추가해주는 번거로움이 따르게 된다.

다시 돌아가서, app-api/src/main/java/com/example/demo 폴더안에 DemoApplication.java 파일을 생성하고 src하위에 있던 내용과 동일하게 작성해준다.

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

그리고 최상위 폴더에 있는 src폴더 자체를 삭제한다.

dependencies {
    // clients 모듈 추가
    implementation project(':clients')
    
    // lombok 추가
    implementation 'org.projectlombok:lombok:1.18.20'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}

마지막으로 build.gradle에 clients 모듈을 추가한다. implementation project(':모듈명')은 해당 모듈에 의존한다는 것을 의미한다. 그리고 테스트를 위해 lombok도 추가해준다.

clients 모듈

app-api 모듈과 마찬가지로 동일한 패키지로 구성한다음 ClientService라는 파일을 하나 만든다. 물론, 테스트 용도이다.

package com.example.demo;

import org.springframework.stereotype.Service;

@Service
public class ClientService {
    public void run() {
        System.out.println("Client Service");
    }
}

그리고 위와 같이 간단하게 코드를 작성해준다.

bootJar {
    enabled = false
}

jar {
    enabled = true
}

그리고 build.gradle을 열고 위 내용을 추가한다. clients 모듈의 경우 라이브러리성 모듈로써 build하기위한 bootJar를 실행하지 말아야하기 때문이다.

테스트

package com.example.demo;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class TestController {
    private final ClientService clientService;

    @GetMapping("/test")
    public String test() {
        clientService.run();
        return "OK";
    }
}

app-api/src/main/java/com/example/demo/TestController.java 파일을 생성하고 위 코드를 작성한 후 URL로 접속해보면 clients 모듈에 작성된 ClientService가 정상적으로 실행되는 모습을 확인할 수 있다.

정리

- 최상위 build.gradle에는 공통적으로 적용될 의존성만 명시하고 모듈별로 필요한 의존성은 각 모듈의 build.gradle에 적어준다.

- 패키지 구조를 동일하게 가져가야 타 모듈에서 작성된 빈을 성공적으로 가져올 수 있다.

- 의존할 모듈을 implementation project(':모듈명') 을 통해 명시해준다.

- 예를 들어 Feign Client를 사용하는 부분을 모듈로 나눈 경우 내부에 Config 느낌의 클래스를 하나 만들고 해당 클래스에 @EnableFeignClients 어노테이션을 붙여줘야 타 모듈에서 사용할 때 빈 관련 오류가 발생하지 않는다. 또한 basePackages 속성에 모듈의 패키지 명을 같이 명시해줘야 한다.

반응형