Javaで書かれたSpring BootプロジェクトをKotlin対応した話

## 背景
NORELチームのエンジニア間で使用技術のモダナイズを進めており、「Java / SpringBootで書かれたシステムをどうせならKotlinで書きたいよね」という話が出てきたので、Kotlinで開発できるように対応を進めています。

最初からKotlinで始める(Gradle)といった記事はよく見かけますが、Javaで書かれている途中からKotlinに対応する(Maven)という記事があまり見つからなかったので、手探りで対応していきました。

## 既存の環境

- macOS High Sierra
- JDK version => 1.8
- Spring Boot => 1.5.8
- Lombokを使用している
- ビルドシステム => Maven
- IntelliJ派とSTS派がいる
 - 私は前者です。

基本的にはSpringBootではREST APIのみを実装しています。

## Kotlin対応させる方針
- 並行でJavaでの開発が進んでいるため、全コードをJavaからKotlinに変換するのは今回は見送り
 - Controller / ServiceはKotlinで書けるようにする
 - JPAなどのデータアクセス層はJavaのまま残しておく
 - 最終的には全部Kotlinにしたい

また、今回の作業はIntelliJ上で行いました。

## 移行作業

### pom.xmlの設定
- pom.xmlの設定は、以下のKotlinの公式サイトを参考にしました。
 - [using-maven](https://kotlinlang.org/docs/reference/using-maven.html)
 - [compiler-plugins](https://kotlinlang.org/docs/reference/compiler-plugins.html)

※今回の投稿に関係ある部分を中心に記載しています。

- Kotlinバージョンの定義

```pom.xml
  <properties>
...
     <java.version>1.8</java.version>
+    <kotlin.version>1.2.10</kotlin.version>
+    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
  </properties>
```

- Kotlinの依存ライブラリを追加

```pom.xml
  <dependencies>
・・・
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-reflect</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-test</artifactId>
+      <version>${kotlin.version}</version>
+      <scope>test</scope>
+    </dependency>
  </dependencies>
```

- ビルド設定

元々`maven-compiler-plugin`は使用していませんでしたが、JavaとKotlinのどちらでも開発ができるように、パッケージをそれぞれ

- src/main/java
- src/main/kotlin
- scr/test/java
- src/test/kotlin

で分けため、`maven-compiler-plugin`を使用しています。

また、Springで使用する各種アノテーションを使用できるようにするため、compilerPluginsとしてspringを定義しています。
ドキュメントにも記載されていますが、springプラグインを定義するとall-openの定義は不要になるようです。

```pom.xml
  <build>
     <finalName>${project.name}</finalName>
-    <sourceDirectory>src/main/java</sourceDirectory>
-    <testSourceDirectory>src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${resources.directory}</directory>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
     </resources>
     <testResources>
       <testResource>
         <directory>src/test/resources</directory>
       </testResource>
     </testResources>
     <plugins>
       <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
           <executable>true</executable>
         </configuration>
       </plugin>
+      <plugin>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <version>${kotlin.version}</version>
+        <configuration>
+          <compilerPlugins>
+            <plugin>spring</plugin>
+          </compilerPlugins>
+        </configuration>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <goals> <goal>compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
+                <sourceDir>${project.basedir}/src/main/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <goals> <goal>test-compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
+                <sourceDir>${project.basedir}/src/test/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+        </executions>
+        <dependencies>
+          <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-maven-allopen</artifactId>
+            <version>${kotlin.version}</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <executions>
+          <!-- Replacing default-compile as it is treated specially by maven -->
+          <execution>
+            <id>default-compile</id>
+            <phase>none</phase>
+          </execution>
+          <!-- Replacing default-testCompile as it is treated specially by maven -->
+          <execution>
+            <id>default-testCompile</id>
+            <phase>none</phase>
+          </execution>
+          <execution>
+            <id>java-compile</id>
+            <phase>compile</phase>
+            <goals> <goal>compile</goal> </goals>
+          </execution>
+          <execution>
+            <id>java-test-compile</id>
+            <phase>test-compile</phase>
+            <goals> <goal>testCompile</goal> </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
  </build>
```

これでIntelliJ上でKotlinを書く準備が整いました。

### STS(Eclipse)の対応

ここまでの対応で普段STSを使用しているメンバーに確認してもらったところ、うまく動作せずに少しハマったのでSTSでの対応方法も記載します。

- Eclipseマーケットプレイスから`Kotlin Plugin for Eclipse`をインストールします。

- `src/main/kotlin`がビルドパスとして認識されていないので、`Projects > Properties > Java Build Path`のSourceからAdd Fileでkotlinのフォルダをチェック。
`src/test/kotlin`も同様

<img width="401" alt="buildPath.png" src="https://qiita-image-store.s3.amazonaws.com/0/24467/c14c4eed-b686-35d6-ad01-2651f46d584c.png">


- プロジェクトを右クリックし、`Configure Kotlin > Add Kotlin Nature`を実行

下図では、既にAdd Kotlin Nature済みのため選択できなくなっていますが、実行前であれば選択できます。

<img width="454" alt="AddKotlinNature.png" src="https://qiita-image-store.s3.amazonaws.com/0/24467/93b4c133-bb38-1923-7b01-74580295f9e2.png">


これでSTS上でもコンパイルが通り、Kotlinでの開発が行えるようになりました。

## Kotlinを書く
### JavaコードからKotlinコードへコンバート

IntelliJのコンバート機能が優秀なので、基本的にはIntelliJでコンバートしました。
一気にまとめては怖いので、1コードもしくは数コードづつ変換しました。

変換したいコードを選択して、`Code > Convert Java File to Kotlin File`でKotlinのコードにコンバートします。

<img width="294" alt="ConvertToKotlin.png" src="https://qiita-image-store.s3.amazonaws.com/0/24467/456b74dc-1711-214e-e7d4-65c6f821e3af.png">

※たまにおかしな変換をするので都度手で修正します。

また、パッケージが`src/main/java`配下のままであるため、`src/main/kotlin`配下の同パッケージ以下に移動します。
これもIntelliJで`Refactor > Move...`でいい感じにリファクタリングしてくれます。

### DI(Autowiredなど)

たとえば以下のコードは

```java
@Service
public class HogeService {
    @Autowired
    private HogeRepository hogeRepository;
}
```

Kotlinでは`lateinit`でも宣言できますが、今回は`constructor injection`で定義するようにしました。

```kotlin
@Service
class HogeService(
    private val hogeRepository: HogeRepository
) {
}
```

### Lombokまわり

Lombokを使っていると、Kotlinにコンバートした際にコンパイルエラーが出ることがありました。

基本的には、Kotlinにコンバートするクラス、Kotlinから参照するクラスは、Delombokすることで解消させました。

- Delombok

IntelliJのlombokプラグインを使用します。
`Refactor > Delombok`からLombok化を解除してくれます。

<img width="825" alt="Delombok.png" src="https://qiita-image-store.s3.amazonaws.com/0/24467/0403f15d-e000-377a-fd99-eadeca5bcd2b.png">


- Slf4jアノテーション

loggerの宣言を省略して、いきなりログ出力するコードを書くことができます。
Kotlinにした場合、以下のコードでlog変数が解決できずコンパイルエラーとなってしまいます。

```java
@Service
@Slf4j
public class HogeService {
    public void hoge() {
        log.info("hoge");
    }
}
```

kotlinでは`companion object`でロガーを初期化することで利用できるようになります。

```kotlin
@Service
class HogeService {
    companion object {
        private val log = LoggerFactory.getLogger(HogeService::class.java)
    }
}
```

アノテーションでのDIの方法などを試されている方もいたので、楽にできるよう検討したいと思います。
http://saiya-moebius.hatenablog.com/entry/2017/11/08/033932

- Dataアノテーション

以下のようなLombok化されたJavaのコードをKotlinから読み、hogeを取得しようとしてもprivateでアクセスでいないと言われてしまいます。
この場合、上に書いたようにDelombok化して対応しました。
（全部KotlinになればそもそもLombok使わなくても・・)

```java
@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
public class Hoge extends AbstractEntity {
	@Column(name = "hoge")
	private String hoge;
}
```

## 今後について

現時点で一部のコードはKotlin化して動作するようになりました。
しかし、実際にコードを書いている途中で問題が起こることが何度かあったので、今後も問題が発生する可能性があります。
そこでの対処方などはどんどん追記していければなと思います。

※Kotlin楽しい!!