spring을 공부해 본 적이 있다면 위와 같은 페이지를 본 적이 있을 것이다. start.spring.io 사이트에 들어가면 위와 같이 다양한 spring의 config 들을 볼 수 있다. 그 외에도 의존성 같은 설정을 손쉽게 추가할 수 있는 것을 알 것이다.
자바를 공부하는 단계라면 단순히 위와 같은 간단한 설정을 통해서 프로젝트를 세팅하고, 스프링 Data JPA나 스프링 시큐리티 등 다양한 의존성을 추가해서 스프링 프로젝트를 .zip 형태로 받아볼 수 있다.
스프링부트를 통해서 다양한 API 서버를 개발하거나, 다른 사람들이 만든 스프링부트 서버를 배포하는 역할을 하게 되면서, dockerfile을 작성하는 과정이나 github actions에서 빌드 툴로써 gradle을 쓰는 데, 특정 레퍼런스를 보면 .xml 확장자로 쓰인 maven을 쓰는 경우가 있었다. 자바 진영에서 크게 쓰이는 빌드 툴은 maven과 gradle인데, 관련된 내용을 조사하면서 얻은 인사이트를 바탕으로 보다 자세히 포스팅하려고 한다.
본론
Ant
초창기 자바의 2000년대 초반의 빌드 툴은 Ant라고 불리는 빌드 도구였다. 아래와 같이 build.xml을 정의했어야 했다.
이 Ant의 단점은 의존성을 관리함에 있어서 별도의 도구가 필요했는데 ( Ivy ) 이러한 문제점도 있었고, 명령형(Imperative)이었다는 점이었다. 이러한 명령형의 단점을 보완하기 위해 추후에 나온 Maven은 선언형(Declarative)이다. 이러한 명령형과 선언형의 개념은 쿠버네티스 등에서도 깊게 다루는 내용이기 때문에 좀 더 깊게 짚고 넘어가면 아래와 같은 문제점이 Ant에 있었다.
명령형(Imperative) vs 선언형(Declarative)
명령형 프로그래밍(Imperative Programming)은 프로그램이 어떻게 동작해야 하는 지를 단계 별로 명시하는 패러다임을 의미한다. 즉, 빌드 스크립트에서 순서에 맞춰서 하나하나씩 명시적으로 코드를 짜야한다는 것이다. 이렇게 하나하나씩 설정해야한다는 점으로 인해 빌드 프로세스의 모든 단계를 세밀하게 설정할 수 있었고, 하드 하게 구현하고 싶다면 깊은 수준의 빌드 로직을 구현할 수 있다는 점이다.
위와 같이 학교에 가기 전에 필요한 작업들이 있다면 이를 꼼꼼하게 설정해주는 것이다. 이로 인해서 세밀하게 빌드 프로세스를 제어할 수 있다는 것을 확인할 수 있겠지만, 조금 복잡하고 귀찮다. 그렇다면 선언형 프로그래밍은 어떨까?
선언형 프로그래밍(Declarative Programming)은 프로그램이 무엇을 해야 하는 지를 기술하는 프로그래밍 패러다임을 의미한다. 즉 실행 방법보다는 결과만 적어두고, 빌드의 프로세스 등은 빌드 툴에 의지하게 되는 것이다. 위와 같이 필요한 것과 도착한 것을 적어두면 프로그램은 알아서 위 조건을 지켜서 알고리즘에 따라 학교에 도착하는 것이다. 위와 비교해 보면 훨씬 간편한 것을 알 수 있고, 만약 선언형으로 설정할 것이 훨씬 많다면, 그만큼 경우의 수가 늘어나기 때문에 몇 배로 편해질 것이다.
위와 같은 부분을 고려하였을 때, 명령형 프로그래밍은 사람들을 지치게 할만했던 것 같다. 우리는 대부분 선언형 프로그래밍을 통해서 의존성의 버전만 저장해두고 있고, npm, yarn, pnpm, pip, gem 등 다양한 패키지 매니저들이 알아서 잘 깔아주기 때문에 직접 하나하나씩 빌드 프로세스를 쓴다는 것은 지금은 조금 경악스러울만하다. 실제로 Ant의 build.xml은 아래와 같다.
<?xml version="1.0" encoding="UTF-8"?>
<project name="MyAntProject" default="compile" basedir=".">
<!-- 설정된 변수들 -->
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<!-- 디렉토리 생성 -->
<target name="init">
<mkdir dir="${build.dir}"/>
</target>
<!-- 컴파일 작업 -->
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}"/>
</target>
<!-- JAR 파일 생성 -->
<target name="jar" depends="compile">
<jar destfile="MyAntProject.jar" basedir="${build.dir}"/>
</target>
<!-- 클린 작업 -->
<target name="clean">
<delete dir="${build.dir}"/>
<delete file="MyAntProject.jar"/>
</target>
</project>
위와 같이 단계 별로 하나하나씩 적어줘야 했는데, 장점도 있겠지만 단점도 확실하게 느껴지는 것을 알 수 있을 것이다. 이러한 것을 개선하기 위해 선언형 프로그래밍 패러다임을 가진 Maven이 등장했다.
Maven
Maven은 Ant의 한계를 보완하기 위해서 등장하였고, 위 주제를 고려하여 가장 큰 특징은 선언형이라는 점이다. 의존성 관리와 표준화된 프로젝트 구조를 제공하는 빌드 도구로, 2004년에 등장했다. 중요한 특징은 중앙 저장소(Maven Central Repository)를 활용하여 라이브러리의 의존성을 자동으로 관리하게 되었고, 이러한 구조는 보편화되어 pip나 npm 등에서 쓰이게 되었다.
maven에서는 pom.xml을 통해서 의존성을 관리하는데, 빠르게 예시 파일을 보이면 아래와 같다.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>MyMavenProject</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<!-- 의존성 예시: JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 플러그인 예시: Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
위와 같이 Maven의 pom.xml 파일을 작성하여 의존성을 명시하는 것만으로 파일을 작성할 수 있었다. 이렇게 의존성 관리의 자동화로 빌드 프로세스가 단순화되었다. 또한 이제는 의존성만 적으면 되기 때문에 이전과 다르게 유지보수의 측면에서도 훨씬 이점이 많았다. 한 끗 차이로 빌드가 실패할 수 있는데, 그러한 의존성 설치는 전적으로 maven이 알아서 해주기 때문이다.
이러한 Maven에도 단점이 약간 있었는데, XML 기반 설정이 가독성이 떨어질 수 있기도 하고, 선언형 패러다임을 그대로 가져가기 때문에 명령형 만큼의 디테일이 떨어지게 된다. 그렇게 나온 다음 빌드 매니저는 Gradle이다.
Gradle
Gradle은 2009년에 등장한 현대적인 빌드 도구로, Ant와 Maven의 장점을 결합하고 단점을 개선하고자 개발되었다. Groovy 또는 Kotlin DSL을 사용하여 빌드 스크립트를 작성할 수 있다.
Maven과 Ivy 리포지토리를 모두 지원하기 때문에, 원하는 방향성에 맞게 명령형과 선언형의 패러다임을 모두 적절히 쓸 수 있다. 이렇게 gradle은 큰 유연함을 가지고 있다.
Maven의 단점인 속도를 개선한 병렬 방식의 빌드와 빌드 캐싱(gradle cache)을 통해서 빠른 빌드 속도를 제공한다.추가로 Incremental builds라는 방식을 사용하는 데, 이로 인해 Maven에 비해 최대 85배의 압도적인 성능이 나오는 것을 확인할 수 있다. 관련된 레퍼런스를 첨부한다.
- [gradle] Gradle vs Maven: Performance Comparison : https://gradle.org/gradle-vs-maven-performance/
Gradle | Gradle vs Maven: Performance Comparison
Gradle is up to 100 times faster than Maven. Take a look at case studies and find out more about the performance advantages of Gradle over Maven.
gradle.org
앞서, gradle은 병렬 빌드를 지원한다고 했던 대로, Multi-project build와 Monolithic app build의 그래프를 위 레퍼런스에서 가져와보았다. 역시 multi-project는 매우 빠른 것을 알 수 있다.
위 그래프를 보면 꽤나 압도적인 성능을 보이는 것을 알 수 있다. 특히 특정 케이스에서는 85배나 빠른 성능을 보인다고 한다.
이렇게 굉장히 강력한 기능을 가지고 있으면서도 마지막 장점은 개발자 친화적이라는 점이다. gradle을 친숙하게 쓰는 사용자라면 xml 파일로 쓰인 설정 파일을 보면 눈이 아프다는 인상을 받을 것이다. ( 나만 그럴 수도 있다 ) gradle을 통해 작성한 build.gradle을 살펴보자.
plugins {
id 'java'
}
group 'com.example'
version '1.0.0'
repositories {
mavenCentral()
}
dependencies {
// 의존성 예시: JUnit
testImplementation 'junit:junit:4.13.2'
}
compileJava {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
위는 간단하게 groovy 문법을 사용해서 작성한 build.gradle이다. xml과 다르게 굉장히 깔끔해진 모습을 볼 수 있다. 이렇게 gradle은 굉장한 강점을 가지고 있기 때문에 현대 개발자들은 gradle을 기본적으로 사용하고 있다.
추가 1 Groovy?
Groovy는 JVM에서 실행되는 객체 지향 프로그래밍 언어이다. Apache Software Foundation에서 관리하고 있기 때문에 Apache Groovy라고 불린다. 간결한 문법이 큰 장점이지만, Java나 Kotlin에 밀려 소스 코드를 작성하기에는 불편한 점이 많기 때문에, 주로 빌드 스크립트 작성에 많이 쓰인다.
추가 2 gradlew?
가끔씩 특정 프로젝트에서 루트 디렉터리에 gradlew라는 파일이 있는 것을 확인할 수 있을 것이다. 이는 Gradle Wrapper로, gradlew라고 불린다. gradle 빌드를 손쉽게 실행할 수 있게 해주는 툴이다.
프로젝트에 사용하는 gradle의 버전을 고정하여, 팀원들 간이나 CI 서버 ( jenkins 및 github actions etc.. )에서 동일한 빌드 환경을 보장해 주는 역할을 하고, gradle의 버전 차이로 인해 발생할 수 있는 자질구레한 빌드 오류를 방지한다.
또한 gradle이 설치되어 있지 않은 경우에는 gradle을 설치하고나서 build.gradle을 실행해야 한다. 근데 여기서 어떠한 버전의 gradle을 설치해야 하는지 알기 쉽지 않다. 이 경우에 gradle wrapper가 자동으로 필요한 gradle 버전을 다운로드하여 빌드를 수행해 준다.
이 외에도 많은 내용들이 있지만 이번 포스팅에서는 간단하게 이 정도만 다룬다. 요약하자면, 다양한 환경에서도 gradle을 통해 빌드를 손쉽게 수행할 수 있게 감싸주는(wrapping)의 역할을 하는 좋은 빌드 도구이다.
마치며
다시 여기로 와보면, Project에 쓰인 Gradle, Maven의 장단점에 대해서 알 수 있을 것이다. 스프링을 정신없이 배울 때에는 뭐가 뭔지 잘 모르고 내가 익숙한 것을 하기에 벅찼지만, 지금은 이러한 디테일을 챙겨 다른 빌드 툴은 뭐고, 왜 사용하지 않는지? 그러한 것들에 대해서 조사하는 시간이 중요하다고 느끼게 되었다.
그리고, 자바에 대해서 조사하다보면 고고학자가 된 기분이 든다. 1995년의 썬 마이크로시스템즈에서부터 시작하는 역사들과 개발 패러다임을 느낄 수 있는데, 딱딱하다고 생각해서 별로 안 내키는 Java의 역사를 알고 보니 산전수전을 다 겪어 고도로 개조된 프로그래밍 언어라는 생각이 든다.. 생각보다 재미있는 친구라는 결론.
참고
- [gradle.org] Gradle vs Maven : https://gradle.org/gradle-vs-maven-performance/
- [seongjin] 쿠버네티스에서 명령형 접근법과 선언형 접근법의 차이 이해하기 : https://seongjin.me/kubernetes-imparative-vs-declarative/
감사합니다.
'Language > Java' 카테고리의 다른 글
Java&spring의 멀티스레드 작동 방식 ( feat. HikariCP, Tomcat ) (1) | 2025.01.28 |
---|---|
자바(Java)의 역사에 대해서 (1) | 2024.01.06 |