Javaで書かれたSpring APIをKotlinで書いて比較してみた

# 前書き

今までSpring Bootを使って`Java`で書いていたAPIを、
`Kotlin`ではどう書くのか調べて実装してみたので、その備忘録。

# 記事概要

いわゆる階層構造システムの基本となる、以下の実装について比較してみる。

* Entity
* Controller
* Service
* Repository

## 環境

* Spring Boot 2.4.0
* JavaコードはLombokを使用する

## 共通ポイント

Javaと比較した時のKotlinを理解する上での（個人的な）ポイントは、

* `val/var`の意図（valは読み取り専用、varは読み書き両方可能）
* デフォルトの修飾子は`public`
 * Javaでの`public class`→`class`のみで同義に
* コンストラクタの書き方
 * コンストラクタに関しては色々な固有機能があるみたいなのですが、ここでは実装のための書き方のみ記します
* カプセル化のためのgetter/setterは自動で作ってくれる（Lombokなどの外部ライブラリではなく仕様として）
* 文末に`;`付けない

## Entity

### Java

```Java
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;

@Entity
@Table(name = "english_word")
@Data
public class EnglishWordJava {
  // ID
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  // 単語名
  private String word;
  // 意味
  private String meaning;
  // 作成日時
  @CreationTimestamp private LocalDateTime createTime;
  // 更新日時
  @UpdateTimestamp private LocalDateTime updateTime;
}
```

### Kotlin

```Kotlin
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp
import java.time.LocalDateTime
import javax.persistence.*

@Entity
@Table(name = "english_word")
data class EnglishWord(
        // ID
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val id: Long = 0,
        // 単語名
        var word: String = "",
        // 意味
        var meaning: String = "",
        // 作成日時
        @CreationTimestamp val createTime: LocalDateTime = LocalDateTime.now(),
        // 更新日時
        @UpdateTimestamp
        val updateTime: LocalDateTime = LocalDateTime.now()
)
```

### メモ

* データクラスと言うKotlinにおける仕様を用いている。(`data class ○○`)
* 初期値を明示している理由は、インスタンス生成時に引数を入れたくないため。（データクラスでは（色々記述しないと）全メンバを引数にとるコンストラクタのみしか生成されないため）
 * なお普通のクラスでも同様の実装は可能なのでお好みで良いかも
 * 明示的に`val/var`を分けているがそうする必要があるかはわかっていない。。

### （おまけ）登録Form

わざわざ記載するほどのものではないですが一応。

#### Java

```Java
import lombok.Data;

@Data
public class EnglishWordJavaForm {
  // 単語名
  private String word;
  // 意味
  private String meaning;
}
```

#### Kotlin

```Kotlin
data class EnglishWordForm(
        //　単語名
        val word: String,
        // 意味
        val meaning: String
)
```

## Controller

### Java

```Java
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/* 自作クラスのimportは割愛 */

@RestController
@RequestMapping(path = "/api")
@RequiredArgsConstructor
public class EnglishWordJavaController {

  private EnglishWordJavaService englishWordJavaService;

  /** 取得API */
  @GetMapping("/word")
  @ResponseStatus(HttpStatus.OK)
  public List<EnglishWordJava> getEnglishWord() {
    return englishWordJavaService.getEnglishWord();
  }

  /** 登録API */
  @PostMapping("/word")
  @ResponseStatus(HttpStatus.CREATED)
  public void registerEnglishWord(@RequestBody EnglishWordJavaForm englishWordJavaForm) {
    englishWordJavaService.registerEnglishWord(englishWordJavaForm);
  }
}
```

### Kotlin

```Kotlin
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
/* 自作クラスのimportは割愛 */

@RestController
@RequestMapping(path = ["/api"])
class EnglishWordController(private val englishWord: EnglishWordService) {

    /**
     * 取得API
     */
    @GetMapping("/word")
    @ResponseStatus(HttpStatus.OK)
    fun getEnglishWord(): List<EnglishWord> {
        return englishWordService.getEnglishWord()
    }

    /**
     * 登録API
     */
    @PostMapping("/word")
    @ResponseStatus(HttpStatus.CREATED)
    fun registerEnglishWord(@RequestBody englishWordForm: EnglishWordForm) {
        topService.registerEnglishWord(englishWordForm)
    }
}
```

### メモ

* サービス呼ぶ処理が簡潔になった
* `void`を表現するには、型を指定しないだけで良いよう

## Service

### Java

```Java
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/* 自作クラスのimportは割愛 */

@Service
@RequiredArgsConstructor
public class EnglishWordJavaService {

  private EnglishWordJavaRepository englishWordJavaRepository;

  /** データを全て取得 */
  public List<EnglishWordJava> getEnglishWord() {
    return englishWordJavaRepository.findAll();
  }

  /** 登録 */
  public void registerEnglishWord(EnglishWordJavaForm englishWordJavaForm) {
    EnglishWordJava englishWordJava = new EnglishWordJava();
    englishWordJava.setWord(englishWordJavaForm.getWord());
    englishWordJava.setMeaning(englishWordJavaForm.getMeaning());
    englishWordJavaRepository.save(englishWordJava);
  }
}
```

### Kotlin

```Kotlin
import org.springframework.stereotype.Service
/* 自作クラスのimportは割愛 */

@Service
class EnglishWordService(private val englishWordRepository: EnglishWordRepository) {

    /**
     * データを全て取得
     */
    fun getEnglishWord(): List<EnglishWord> = englishWordRepository.findAll()

    /**
     * 登録
     */
    fun registerEnglishWord(englishWordForm: EnglishWordForm) {
        val englishWord = EnglishWord()
        englishWord.word = englishWordForm.word
        englishWord.meaning = englishWordForm.meaning
        englishWordRepository.save(englishWord)
    }
}
```

### メモ

* 関数を定義する際、処理が１行のみなら波括弧`{}`を省略して書ける
* getter/setterの呼び出し方が若干違う

## Repository

### Java

```Java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/* 自作クラスのimportは割愛 */

@Repository
public interface EnglishWordJavaRepository extends JpaRepository<EnglishWordJava, Long> {}
```

### Kotlin

```Kotlin
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
/* 自作クラスのimportは割愛 */

@Repository
interface EnglishWordRepository : JpaRepository<EnglishWord, Long> {}
```

# 後書き

ざっくりとSpring Bootを使ってKotlinでAPI実装の記述をしてみました。
まだ使って間もないので、Kotlin特有の機能などもっと調べて記事にできたらいいなと思ってます。

記述量を大幅に減らせるので、慣れたら大分楽になる印象。
性能面でもいつか比較してみたいな（願望）

# 参考

* https://qiita.com/ozaki25/items/301d43dfcb1903ef995b
* https://qiita.com/SYABU555/items/7cc5909c119ec9ab0288
* https://qiita.com/farman0629/items/f959a4ff791f26b33fc3
* https://qiita.com/yut_arrows/items/203fb544ff52d89d7e3f