해당 연재에서는, Build Tool로  Gradle, Framework는 Spring, Language는 Kotlin을 사용해 Application을 구현하는 과정을 살펴보면서, Kotlin을 사용하면서 어떠한 점이 좋은지, 어떠한 점이 불편한지, 유의할 점은 무엇인지를 공유하려고 합니다.

반드시 Kotlin에 대해서만 다루지 않고 구현 과정에서 필요한 모든 사항과 고민들을 함께 나누려 하니 어떤 의견이든 댓글로 남겨주시기 바랍니다.

Gradle Project

제일 먼저 할 일은 프로젝트를 위한 디렉토리를 생성한 뒤, 해당 디렉토리에 Gradle Wrapper를 설치하는 것입니다. 이 과정에서는 사전에 Gradle이 설치되어 있어야 합니다.

$ mkdir kotring
$ cd kotring
$ gradle wrapper --gradle-version 5.5 --distribution-type all

그러면 .gradle, gradle 디렉토리, gradlew, gradlew.bat 파일이 생성됩니다. 이제부터 우리는 Gradle Wrapper(gradlew)를 사용할 것입니다.

이제 Gradle이 사용할 build script 파일을 만들어야 하는데, 이를 위해 init task를 사용합니다.

$ ./gradlew init --dsl kotlin

그러면, build.gradle.kts 파일과 settings.gradle.kts 파일이 생성됩니다.

Kotlin DSL

Gradle의 기본 build script는 Groovy DSL 입니다. Gradle은 5.0 버전부터 Kotlin DSL도 지원하기 시작했습니다. init task를 실행할 때 --dsl 옵션을 사용하면 DSL을 특정할 수 있는데 여기서는 취지에 맞게 kotlin 을 사용했습니다.

이번 글에서는 이 Kotlin DSL을 주제로 하려고 합니다. Gradle에 친숙하지 않더라도 최대한 이해하기 쉽게 설명하도록 하겠습니다.

Plugins

앞서 예고한대로 Spring Framework를 사용하려고 하는데, 그 중에서도 엄청난 편의성을 제공하는 Spring Boot를 사용하려고 합니다.

Spring 팀은 Gradle에서 Spring과 관련된 의존성을 Maven-like하게 관리해주는 플러그인인 io.spring.dependency-management 을 제공합니다. 또, Spring Boot를 위한 org.springframework.boot 플러그인도 제공하고 있습니다.

이 두 가지 플러그인이 있으면, Spring Boot와 Spring Boot가 공식적으로 지원하는 서드 파티 라이브러리들의 의존성을 매우 쉽게 관리할 수 있습니다.

플러그인을 관리하기 위해서는 plugins DSL을 사용해야 합니다.

...

plugins {
    id("org.springframework.boot") version "2.1.6.RELEASE"
    id("io.spring.dependency-management") version "1.0.8.RELEASE"
}

...
build.gradle.kts

plugins 블록 내에서는 몇 가지 DSL이 지원되는데 그 중 하나가 id 입니다. 이 DSL은 특정 플러그인의 아이디를 파라메터로 받는 함수입니다. 위에서 보다시피, 앞서 말한 두 플러그인을 해당 DSL을 이용해 선언했습니다. 플러그인을 선언할 때는 반드시 버전을 명시해야 하는데, 이 때 사용하는 것인 바로 version 이라고 하는 중위 연산자(infix)입니다.

Managing Plugins

Gradle은 플러그인을 손쉽게 관리할 수 있는 DSL을 제공하는데, 여기서 몇 가지를 소개해볼까 합니다.

gradlew init 으로 만들어진 파일 중에는 settings.gradle.kts라는 파일이 존재하는데, 이 파일에는 멀티 프로젝트를 위한 빌드를 구성하고 동적 프로퍼티를 설정할 수 있습니다.

이 섹션에서 알아보고자 하는 내용은 플러그인이 해석되는 방식을 결정할 수 있는 PluginManagementSpec에 대한 내용을 살펴보고자 합니다. 아래는 PluginManagementSpec 이 제공하는 pluginManagement 블록에 대한 예제입니다.

pluginManagement {
    repositories {
        gradlePluginPortal()
    }
    resolutionStrategy {
        eachPlugin {
            if (requested.id.namespace != null) {
                if (requested.id.namespace!!.startsWith("org.springframework")) { // Spring
                    useVersion("2.1.6.RELEASE")
                }
                if (requested.id.namespace!!.startsWith("io.spring")) {
                    useVersion("1.0.8.RELEASE")
                }
            }
        }
    }
}
settings.gradle.kts

pluginManagement 블록은 두 가지 메서드를 제공하는데, 하나는 repositories 이고, 다른 하나는 resolutionStrategy 입니다.

repositories DSL은 Gradle 플러그인 저작물(artifact)를 위한 저장소를 결정하기 위해 제공됩니다. 여기서는 gradlePluginPortal() 이라고 하는 사전에 정의되어 있는 저장소를 사용하고 있습니다(https://plugins.gradle.org/).

resolutionStrategy DSL에서는 각각의 플러그인에 대한 세부적인 설정을 할 수 있습니다. 예제를 보면 eachPlugin 이라는 블록을 사용 중인데, 각각의 플러그인을 해석하는 과정에서 실행될 액션을 정의할 수 있습니다(Gradle 5.5 버전까지는 PluginResolutionStrategyeachPlugin 메서드만 제공하고 있습니다).

eachPlugin 블록 내에서 실행 가능한 액션은 PluginResolveDetails 라는 인터페이스를 구현해야 하는데, 이 인터페이스는 getRequested(), useModule(Object notation), useVersion(String version), getTarget() 이라는 메서드를 제공합니다.

주요 사용 흐름은 getRequested() (Kotlin의 축복(?!)으로 마치 변수인듯 requested 라고 호출 가능합니다)를 통해 현재 요청된 플러그인을 가져와 useModule() 이나 useVersion() 을 통해 모듈의 그룹 아이디, 아티팩트 아이디, 버전을 변경하거나 버전만 따로 정의해줄 수 있습니다.

우리는 이전 섹션에서 org.springframework.bootio.spring.dependency-management 라는 플러그인을 설정했는데, 이 때 각각의 버전을 정의해주었습니다. 하지만 projectManagement DSL 덕분에 플러그인 버전을 별도로 정의할 수 있으므로 다음과 같이 build.gradle.kts 스크립트에서는 버전 정보를 신경쓰지 않도록 숨길 수 있습니다.

이렇게 하면 Spring Framework의 버전을 settings.gradle.kts로 관리할 수 있습니다. 물론 Gradle Plugin을 직접 작성한다면 이보다 더 좋은 방법으로 관리할 수 있겠지만, 여기서는 그러한 방법보다는 Gradle과 Kotlin DSL을 있는 그대로 활용하는 방법을 소개하고자 했습니다.

다음 글에서는 추가된 플러그인을 프로젝트에 적용하고 프로젝트에 대한 의존성을 선언하는 방법에 대해서 논의해보겠습니다.