SpringBoot 프로젝트의 build.gradle 파일에서 'implementation'과 'runtimeOnly' 설정의 차이점과 사용법을 알아보자
📌 서론
Gradle의 빌드 스크립트인 build.gradle에서 의존성을 관리할 때 runtimeOnly와 implementation은 자주 사용되는 구성이야. 각각의 키워드가 어떤 목적으로 사용되는지, 그리고 차이점은 무엇인지 알아보자.
글의 이해를 위해 가장 먼저 스프링의 "모듈"을 알아보자
1. 스프링 부트의 모듈 이해하기
1-1. 메인 모듈 (Main Module)
- 애플리케이션의 핵심 기능을 담당한다. 주요 비즈니스 로직, 데이터베이스 연결, 웹 컨트롤러 등을 포함한다.
- 프로젝트의 src 폴더 내에 위치한 Java 패키지들, application.properties 파일 등이 "메인 모듈"에 포함된다.
main-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.example.mainproject/
│ │ │ ├── MainApplication.java (애플리케이션 시작점)
│ │ │ ├── security/ (보안 관련 클래스)
│ │ │ ├── controller/ (웹 컨트롤러)
│ │ │ ├── service/ (서비스 로직)
│ │ │ └── repository/ (데이터베이스 연결)
│ │ └── resources/
│ │ └── application.properties (환경 설정 파일)
└── build.gradle (빌드 설정 파일)
1-2. 서브 모듈 (Sub Module)
- 메인 모듈과는 별개의 기능을 수행하거나, 메인 모듈의 기능을 확장한다.
- 다른 기능을 담당하는 별도의 웹 서비스, 라이브러리, 혹은 메인 프로젝트의 API를 사용하는 클라이언트 애플리케이션이 서브 프로젝트가 될 수 있다.
sub-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.example.subproject/
│ │ │ └── SubApplication.java (서브 프로젝트 시작점)
│ │ └── ... (추가 서브 프로젝트 관련 폴더 및 파일)
└── build.gradle (빌드 설정 파일)
1-3 멀티 모듈 프로젝트 (Multi-Module Project)
- 하나의 프로젝트 내에서 메인 모듈과 하나 이상의 서브 모듈을 포함하는 구조다.
root-project/
├── main-module/ (메인 모듈)
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com.example.mainproject/
│ │ │ │ ├── MainApplication.java (애플리케이션 시작점)
│ │ │ │ ├── security/ (보안 관련 클래스)
│ │ │ │ ├── controller/ (웹 컨트롤러)
│ │ │ │ ├── service/ (서비스 로직)
│ │ │ │ └── repository/ (데이터베이스 연결)
│ │ │ └── resources/
│ │ │ └── application.properties (환경 설정 파일)
│ └── build.gradle (메인 모듈 빌드 설정)
│
├── sub-module/ (서브 모듈)
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com.example.subproject/
│ │ │ │ └── SubApplication.java (서브 프로젝트 시작점)
│ │ │ └── ... (추가 서브 프로젝트 관련 폴더 및 파일)
│ └── build.gradle (서브 모듈 빌드 설정)
│
└── build.gradle (루트 프로젝트 빌드 설정)
멀티 모듈 프로젝트에 의존성을 추가시켜 보자
1-4. 멀티 모듈 프로젝트에 web, security 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
}
1. spring-boot-starter-web |
|
2. spring-boot-starter-security |
|
1-5. 멀티 모듈 프로젝트 트리 구조
root-project/
├── main-module/ (메인 모듈)
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com.example.mainproject/
│ │ │ │ ├── MainApplication.java (애플리케이션 시작점)
│ │ │ │ ├── security/ (보안 관련 클래스)
│ │ │ │ ├── controller/ (웹 컨트롤러)
│ │ │ │ ├── service/ (서비스 로직)
│ │ │ │ └── repository/ (데이터베이스 연결)
│ │ │ └── resources/
│ │ │ └── application.properties (환경 설정 파일)
│ └── build.gradle (메인 모듈 빌드 설정)
│ └── dependencies {
│ ├── implementation 'org.springframework.boot:spring-boot-starter-security'
│ └── implementation 'org.springframework.boot:spring-boot-starter-web'
│ }
│
├── sub-module/ (서브 모듈)
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── com.example.subproject/
│ │ │ │ └── SubApplication.java (서브 프로젝트 시작점)
│ │ │ └── ... (추가 서브 프로젝트 관련 폴더 및 파일)
│ └── build.gradle (서브 모듈 빌드 설정)
│
└── build.gradle (루트 프로젝트 빌드 설정)
1-6. 의존성 관리 및 실행 방식
- 1. 메인 모듈 (main-module)
build.gradle 설정 |
|
의존성의 범위 |
|
- 2. 서브 모듈 (sub-module)
메인 모듈과의 관계 |
|
의존성 접근 제한 |
|
예시를 통해 의존성 접근 제한을 이해해보자
- 예시: 서브 모듈과 메인 모듈의 상호작용
1. 메인 모듈 (Main Module) |
|
2. 서브 모듈 (Sub Module) |
|
- 상황 설명
1. 메인 모듈의 기능 |
|
2. 서브 모듈의 활용 |
|
- 메인 모듈 코드
// Main Module
@RestController
public class MainController {
@GetMapping("/data")
public String getData() {
return "Data from Main Module";
}
}
- 서브 모듈 코드
- 서브 모듈은 메인 모듈의 /data 엔드포인트를 호출하여 데이터를 가져오지만, MainController 클래스에는 접근할 수 없다.
// Sub Module
public class SubModuleService {
public void useMainModuleApi() {
// 서브 모듈에서 메인 모듈의 REST API 호출
String data = restTemplate.getForObject("http://localhost:8080/data", String.class);
// ... 데이터 처리 로직
}
}
- 의존성과 독립성
1. 의존성 |
|
2. 독립성 |
|
의존성 접근 제한이 이해되었다면 마지막으로 모듈의 실행 방식을 알아보자
- 3. 실행 방식
독립적 실행 |
|
통합된 실행 |
|
지금까지의 설명으로 "모듈"을 이해했다면 implementation을 알아보자
2. Implementation 이해하기
다음 그래프에서는 Java Library 플러그인을 사용할 때 구성을 설정하는 방법을 설명한다.
2-1. implementation이란?
- implementation 구성은 프로젝트를 빌드할 때 필요한 의존성을 정의한다. 이 의존성은 프로젝트의 빌드 결과물에 포함되기 때문에, 상용 환경에서 해당 JAR 파일을 배포하면 필요한 라이브러리가 함께 제공된다.
- 예를 들어, Spring Boot 프로젝트에서 JPA를 사용하려면 다음과 같이 implementation 구성을 사용할 수 있다. 이렇게 하면 Spring Boot는 JPA를 사용하기 위한 모든 의존성을 자동으로 포함한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
2-2. implementation을 사용하면 얻을 수 있는 장점
1. 결합도 감소 |
|
2. 의존성의 명확성 |
|
3. 프로젝트 확장 준비 |
|
2-3. 단일 모듈 프로젝트의 관점
- 단일 모듈 프로젝트, 즉 루트 모듈만 있는 경우에도 implementation 사용은 중요하다.
1. 내부 의존성 관리 |
|
2. 의존성의 명확성 |
|
3. 추후 확장을 위한 준비 |
|
결론적으로, implementation 의존성은 멀티 모듈 프로젝트뿐만 아니라 단일 모듈 프로젝트에서도 효과적인 의존성 관리 방법이다. 프로젝트의 규모나 복잡성에 관계없이 일관된 의존성 관리를 통해 프로젝트의 유지보수성과 확장성을 개선하는 데 도움이 된다.
지금부터 예시를 통해 implementation을 알아보자
3. Implementation 예시
3-1. 스프링 부트 웹 애플리케이션 개발
- implementation 'org.springframework.boot:spring-boot-starter-web'은 프로젝트에 필요한 스프링 MVC, 임베디드 톰캣 등의 웹 개발 컴포넌트를 제공한다. 이는 웹 애플리케이션 개발에 필수적인 요소들을 포함한다.
dependencies {
// 스프링 부트 스타터 웹은 웹 애플리케이션 개발에 필요한 스프링 MVC, 톰캣 등을 제공
implementation 'org.springframework.boot:spring-boot-starter-web'
}
3-2. Implementation 의존성의 내부 사용
- implementation으로 추가된 의존성은 해당 모듈 내에서만 사용되고, 모듈 외부로 노출되지 않는다. 이는 모듈 간 결합도를 낮추고, 각 모듈의 독립성을 유지하는 데 도움을 준다.
3-3. 모듈 간 의존성과 독립성
- 예를 들어, 모듈 A가 spring-boot-starter-web을 implementation으로 포함하고 있고, 모듈 B가 모듈 A에 의존하는 경우, 모듈 B는 모듈 A의 기능을 사용할 수 있지만, spring-boot-starter-web의 클래스나 메소드를 직접 사용할 수는 없다. 이로 인해 각 모듈의 독립성이 유지되고, 모듈 간 의존성이 줄어든다.
이 내용을 조금 더 자세히 알아보자
- 예시 모듈의 구조는 다음과 같다.
루트 모듈 (Root Module) |
|
메인 모듈 (Module A) |
|
서브 모듈 (Module B) |
|
- 멀티 모듈 프로젝트에서 메인 모듈(A)에 implementation 'org.springframework.boot:spring-boot-starter-web'로 선언된 경우, 이 의존성(여기서는 spring-web 관련 기능)은 메인 모듈 내에서만 사용 가능하다. 따라서 서브 모듈(B)에서는 메인 모듈(A)에 구현된 @Controller 같은 클래스나 어노테이션에 직접 접근할 수 없다.
- 이렇게 구성하는 이유는 각 모듈의 기능과 의존성을 명확하게 분리하고, 모듈 간의 결합도를 낮추기 위함이다. 서브 모듈(B)에서 spring-web 관련 기능이 필요한 경우, 서브 모듈 자체의 build.gradle 파일에 spring-boot-starter-web을 독립적으로 추가해야 한다.
- 이는 서브 모듈이 자신만의 의존성을 관리하고, 필요한 기능을 독립적으로 제공할 수 있도록 하여, 전체 프로젝트의 유지보수성과 확장성을 향상시키는 방식이다.
3-4. 프로젝트 규모와 의존성 관리의 중요성
- 프로젝트 규모가 커질수록, implementation을 통한 의존성 관리의 중요성이 커진다. 이 방식은 각 모듈이 필요한 부분만을 사용하게 하여 불필요한 의존성으로 인한 문제를 피하고, 프로젝트의 확장성과 유지보수성을 향상시킨다.
다음으로 runtimeOnly에 대해서 알아보자
4. runtimeOnly 이해하기
4-1. runtimeOnly의 역할
- runtimeOnly 의존성은 프로젝트 실행 시에만 필요하며, 컴파일 시점에는 필요하지 않은 의존성을 관리하는 데 사용된다. 이는 빌드 과정을 최적화하고, 필요하지 않은 의존성을 제외함으로써 컴파일 시간을 단축시키는 효과가 있다.
4-2. runtimeOnly의 장점
- 런타임에 필요한 의존성을 분리함으로써, 애플리케이션의 시작 시간과 전반적인 성능이 개선된다. 서버나 클라우드 환경에서 애플리케이션을 운영할 때 특히 중요한 요소로, 데이터베이스 드라이버나 로깅 프레임워크와 같은 의존성이 이에 해당된다.
- 예를 들어, MySQL JDBC 드라이버는 애플리케이션이 데이터베이스와 상호작용하는 런타임 시에는 필수적이지만, 컴파일 시에는 필요하지 않다. 이러한 의존성을 runtimeOnly로 선언함으로써, 애플리케이션의 컴파일 과정은 더 간소화되며, 런타임에만 필요한 리소스를 로드하여 애플리케이션의 효율성을 높일 수 있다.
runtimeOnly는 어떻게 사용될까?
5. runtimeOnly 예시
5-1. 데이터베이스 접근을 위한 JDBC 드라이버 설정
dependencies {
// MySQL 데이터베이스에 접근하기 위한 JDBC 드라이버
runtimeOnly 'mysql:mysql-connector-java'
}
5-2. MySQL JDBC 드라이버의 역할
- "mysql:mysql-connector-java"는 MySQL 데이터베이스와의 통신을 위한 JDBC 드라이버로, 이 드라이버는 Java 애플리케이션이 데이터베이스와 통신하는 데 필수적이다. runtimeOnly로 지정함으로써, 런타임 시에만 이 드라이버가 로드되고 사용되며, 컴파일 시에는 포함되지 않는다.
5-3. RuntimeOnly의 중요성
- 이 드라이버를
runtimeOnly
로 지정하는 이유는, 애플리케이션의 컴파일 시에는 실제로 데이터베이스와의 연결이 필요하지 않기 때문이다. 컴파일 시에는 데이터베이스 접근 로직의 올바른 구문과 타입만 확인하면 되고, 실제 데이터베이스 연결은 필요하지 않다.
5-4. 런타임 시의 작동 방식
- 하지만 애플리케이션이 실행되는 런타임에서는 다르다. 애플리케이션이 데이터베이스에 데이터를 조회하거나 저장하는 등의 작업을 할 때, 이 JDBC 드라이버가 필수적으로 필요하다. 이 드라이버가 없으면 Java 애플리케이션은 MySQL 데이터베이스와 통신할 수 없다.
이렇게 runtimeOnly를 사용함으로써 빌드 시간과 결과물의 크기를 최적화하고, 필요한 시점에만 특정 의존성을 로드하는 효율적인 방법을 구현할 수 있다.
6. Implementation과 RuntimeOnly: 차이점과 사용 상황
6-1. implementation 사용법과 특징
- implementation 의존성은 프로젝트의 내부 모듈에서만 사용되며, 외부 모듈에는 노출되지 않는다. 이는 컴파일 시간에만 필요하며, 최종 빌드 결과물에는 포함되지 않는다.
1. 모듈 간 의존성 감소 |
|
2. 변경의 영향 최소화 |
|
6-2. runtimeOnly의 역할과 중요성
- runtimeOnly 의존성은 컴파일 시점에는 필요하지 않지만, 런타임 시 필요한 의존성을 정의한다. 이는 데이터베이스 드라이버나 로깅 프레임워크와 같이 실행 시에만 필요한 라이브러리들에 주로 사용된다.
1. 컴파일 시간 단축 |
|
2. 빌드 결과물 최적화 |
|
6-3. 결론: 의존성 관리의 중요성
- implementation과 runtimeOnly는 각각의 상황과 요구사항에 맞게 사용되어야 한다. 올바른 의존성 관리는 프로젝트의 성능 최적화, 유지보수의 용이성, 그리고 코드 품질 향상에 결정적인 역할을 한다. 이 두 의존성 유형을 적절히 활용함으로써, 효율적이고 유지보수가 용이한 프로젝트 구조를 만들 수 있다.
지금까지 build.gradle에 라이브러리 의존성을 작성하는 2가지 방법에 대해서 알아봤다. 평상시에는 신경 쓰지 않아서 몰랐는데 프로메테우스를 사용하기 위해 gradle에 의존성을 추가하던 중 runtimeOnly라는 다른 방식으로 작성하도록 되어있어서 바로 조사를 해봤고 이를 정리해서 공유한다. 이 글을 읽는 독자분께도 좋은 지식이 되었으면 한다.
gradle의 작동 원리가 궁금하다면? 👇🏻👇🏻
'Spring 기초 > Spring 설정' 카테고리의 다른 글
[Spring] 스프링 빈 설정의 진화: XML에서 자바, 그리고 컴포넌트 기반으로 (0) | 2024.08.23 |
---|---|
[Spring] Gradle 이해하기 (0) | 2023.11.08 |
[Spring] yml vs properties 설정파일 비교 (1) | 2023.11.08 |
[Spring] Maven이란? (0) | 2023.11.08 |
Spring Boot: Gradle 버전 확인하기 (0) | 2023.10.26 |