Spring Boot 2 (Kotlin) + Gradle Kotlin DSL でマルチモジュールを実現してみた 

# 背景
JavaでSpringBootを触る機会は多かったのですが、
[Spring Initializr](https://start.spring.io/) もKotlinが選択できるようになっているし、
いつかはKotlinで書いてみるつもりでした。
やってみたらKotlin云々よりもマルチモジュールのgradleの書き方で
少しハマったので備忘として残しておきます。

# Gradle Kotlin DSL とは
GradleのビルドスクリプトをKotlinで書けるようになります。
詳しくは[このあたり](https://docs.gradle.org/current/userguide/kotlin_dsl.html)を参照。
Spring Initializrでプロジェクト作ると最初からKotlin DSLで書かれています。

# バージョン
|software|version|
|:-------|:------|
|Spring Boot|2.4.1|
|JDK|Corretto-11.0.9.12.1|
|Kotlin|1.4.21|
|Gradle|6.7.1|

# マルチモジュール構成
以下のようなWebとBatchがCoreを参照するマルチモジュールとする。
![MultiModuleImage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361154/df3c03bc-11c4-45c6-653f-436e6e832e1f.png)

```
.
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── practice-batch
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       │   ├── kotlin
│       │   │   └── com
│       │   │       └── example
│       │   │           └── practice
│       │   │               └── batch
│       │   │                   └── PracticeBatchApplication.kt
│       │   └── resources
│       │       └── application.yml
│       └── test
│           └── kotlin
│               └── com
│                   └── example
│                       └── practice
│                           └── batch
│                               └── PracticeBatchApplicationTests.kt
├── practice-core
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       │   └── kotlin
│       └── test
│           ├── kotlin
│           │   └── com
│           │       └── example
│           │           └── practice
│           │               └── core
│           │                   ├── PracticeCoreApplication.kt
│           │                   └── PracticeCoreApplicationTests.kt
│           └── resources
│               └── application.yml
├── practice-web
│   ├── backend
│   │   └── src
│   │       ├── main
│   │       │   ├── kotlin
│   │       │   │   └── com
│   │       │   │       └── example
│   │       │   │           └── practice
│   │       │   │               └── web
│   │       │   │                   └── PracticeWebApplication.kt
│   │       │   └── resources
│   │       │       └── application.yml
│   │       └── test
│   │           └── kotlin
│   │               └── com
│   │                   └── example
│   │                       └── practice
│   │                           └── web
│   │                               └── PracticeWebApplicationTests.kt
│   └── build.gradle.kts
└── settings.gradle.kts
```

# ビルドスクリプト
## build.gradle.kts
ルートプロジェクトのビルドスクリプト。

```gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.plugin.SpringBootPlugin
import org.springframework.boot.gradle.tasks.bundling.BootJar

plugins {
  id("java")
  id("org.springframework.boot") version "2.4.1"
  id("io.spring.dependency-management") version "1.0.10.RELEASE"
  kotlin("jvm") version "1.4.21"
  kotlin("plugin.spring") version "1.4.21"
}

tasks.getByName<BootJar>("bootJar") {
  enabled = false  // ルートプロジェクトで実行Jarを作成しない設定
}

tasks.getByName<Jar>("jar") {
  enabled = false  // ルートプロジェクトでJarを作成しない設定
}

allprojects {
  repositories {
    mavenCentral()
  }
}

subprojects {
  group = "com.example"
  version = "0.0.1-SNAPSHOT"

  apply(plugin = "kotlin")
  apply(plugin = "org.jetbrains.kotlin.plugin.spring")
  apply(plugin = "io.spring.dependency-management")

  dependencyManagement {
    imports {
      mavenBom(SpringBootPlugin.BOM_COORDINATES)
    }
  }

  java.sourceCompatibility = JavaVersion.VERSION_11
  java.targetCompatibility = JavaVersion.VERSION_11

  configurations {
    compileOnly {
      extendsFrom(configurations.annotationProcessor.get())
    }
  }

  dependencies {
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    // 以下、JUnit5の設定を入れておく
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
      exclude("junit")
    }
    testImplementation("org.junit.jupiter:junit-jupiter-api")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
  }

  tasks.withType<KotlinCompile> {
    kotlinOptions {
      freeCompilerArgs = listOf("-Xjsr305=strict")
      jvmTarget = "11"
    }
  }

  tasks.withType<Test> {
    useJUnitPlatform()
  }
}
```

## setting.gradle.kts
```gradle
rootProject.name = "practice-parent"

include(":practice-core")
include(":practice-batch")
include(":practice-web")
include(":practice-web:backend")
```

## practice-core/build.gradle.kts
SpringBootのコンポーネントを実装できる共通的なライブラリプロジェクトのビルドスクリプト。

```gradle
import org.springframework.boot.gradle.tasks.bundling.BootJar

apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
  implementation("org.springframework.boot:spring-boot-starter")
}

tasks.getByName<BootJar>("bootJar") {
  enabled = false  // practice-coreで実行Jarを作成しない設定
}

tasks.getByName<Jar>("jar") {
  enabled = true
}
```

## practice-web/build.gradle.kts
配下に複数のモジュールがある場合は作っておいても良いかも。

```gradle
tasks.getByName<Jar>("jar") {
  enabled = false
}
```

## practice-web/backend/build.gradle.kts
practice-coreに依存するWebプロジェクトのビルドスクリプト。

```gradle
import org.springframework.boot.gradle.tasks.bundling.BootJar

apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
  implementation(project(":practice-core"))
  implementation("org.springframework.boot:spring-boot-starter-web")
}

tasks.getByName<BootJar>("bootJar") {
  // 実行Jar用のMainクラスを設定する
  mainClass.set("com.example.practice.web.PracticeWebApplication")
}
```

## practice-batch/build.gradle.kts
practice-coreに依存するBatchプロジェクトのビルドスクリプト

```gradle
import org.springframework.boot.gradle.tasks.bundling.BootJar

apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
  implementation(project(":practice-core"))
  implementation("org.springframework.boot:spring-boot-starter")
}

tasks.getByName<BootJar>("bootJar") {
  // 実行Jar用のMainクラスを設定する
  mainClass.set("com.example.practice.batch.PracticeBatchApplication")
}
```

# コード
## practice-core/src/test/・・・/PracticeCoreApplication.kt
ライブラリプロジェクトなのでsrc/main配下にはメインメソッドを持つクラスは不要。
SpringBootTestを実施できるようにsrc/test配下にメインメソッドを持つクラスを配置する。
メインメソッドはSpringInitializrで作成されたものをそのまま利用。

```kotlin
package com.example.practice.core

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class PracticeCoreApplication

fun main(args: Array<String>) {
  runApplication<PracticeCoreApplication>(*args)
}
```

## practice-web/src/main/・・・/PracticeWebApplication.kt
SpringBootアプリケーションとして起動するためにsrc/main配下にメインメソッドを持つクラスを配置。
`java -jar`で実行できるように、practice-coreとはメインメソッドの書き方を変えている。

```kotlin
package com.example.practice.web

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan

@SpringBootApplication
@ComponentScan("com.example.practice")
class PracticeWebApplication {

  companion object {
    @JvmStatic fun main(args: Array<String>) {
      runApplication<PracticeWebApplication>(*args)
    }
  }
}
```

## practice-batch/src/main/・・・/PracticeBatchApplication.kt
PracticeWebApplicationと同様のため割愛。

# まとめ
とりあえずこれでビルドできました。
マルチモジュールのスタートラインとしては十分だと思います。
以上です。

# 参考サイト
- [マルチモジュールプロジェクトの作成](https://spring.pleiades.io/guides/gs/multi-module/)
- [Kotlin で Spring Boot Web アプリケーションの作成](https://spring.pleiades.io/guides/tutorials/spring-boot-kotlin/)
- [kotlin×SpringBootで作ったアプリケーションの実行可能jarファイル作成方法](https://qiita.com/Arinc0/items/8a80b4ee223e2ae74978)