Spring Boot（Kotlin） + Flywayでサーバサイドアプリケーション作ってみた

アドベントカレンダーもついに17日目です。
クリスマス一週間前ですが、この調子だと今年もクリぼっち確定ですね～(;´д｀)トホホ

普段会社ではLaravelやSAStruts（Java）を触っているんですが、Kotlinでサーバサイドの実装をしたことはなかったので実際にやってみました。

# アプリケーション作成
Androidで使うAPIアプリケーションを想定して作成を進めていきます。
試しにユーザデータの照会、ユーザデータの登録のAPIを作ることにします。

## Spring Bootのインストール
必要なライブラリをbuild.gradleへ書くのは面倒だなー、と思っていたら下記のサイトを見つけました。アプリケーションのひな形を生成してくれるみたいです。

[SPRING INITIALIZR](https://start.spring.io/)

Generate Projectを押してダウンロードしたzipファイルを解凍し、build.gradleのdependenciesへ下記を追加します。

```build.gradle
apply plugin: 'flyway'
:
dependencies {
:
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile 'mysql:mysql-connector-java:5.1.6'
	compile group: 'org.flywaydb', name: 'flyway-core', version: '4.1.0'
:
}
```

## FlywayによるDBマイグレーション
Laravelには便利なマイグレーションの仕組みがありましたが、Spring Bootではどうマイグレーションすればよいのか分かりません。調べてみると、FlywayというDBマイグレーションツールが見つかりました。

`resource/db/migration`配下へSQLファイルを追加します。
SQLのファイルには命名規則が決まっているので注意が必要です。

### マイグレーションファイル命名規則
`V[バージョン番号]__[説明].sql`

* 先頭はV
* [バージョン番号]は1.0.0や1_0など`.`や`_`を組み合わせてバージョンを表現した文字列が入ります。
* [説明]何でもOK
* [バージョン番号]と[説明]の間はアンスコ`_`二つはさまる

```V1.0.0__create_table.sql
-- ユーザテーブル作成
CREATE TABLE IF NOT EXISTS users
(
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    is_deleted BOOLEAN NOT NULL DEFAULT 0,
    created_on DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_on DATETIME ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE=InnoDB;

-- ユーザデータ初期化
INSERT INTO users (name, password) VALUES ("もりもり", "password");
```

### DB接続先情報を追記
application.propertiesに下記を追加して、DBに接続できる状態にしておきます。

```properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://[DBホスト名]:3306/[DB名]
spring.datasource.username=[DBユーザ名]
spring.datasource.password=[DBパスワード]
```

## Entity
DBから取得したデータを格納するEntityクラスを用意します。
`@Entity`でこのクラスがEntityであることを定義、`@Id`でプライマリキーにするプロパティを指定します。
dataクラスで定義する場合、デフォルト値を入れておかないと`InstantiationException: No default constructor for entity`が発生するので注意が必要です。
`@Table`にはテーブル名を指定します。
もし論理削除を使いたいときは`@Where`に論理削除の状態を指定します。

```kotlin
@Entity
@Table(name="users")
@Where(clause="is_deleted=0")
data class User(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name="id", unique = true, nullable = false)
        var id: Long = -1,
        @Column(name="name")
        var name: String = "",
        @Column(name="password")
        var password: String = "",
        @Column(name="created_on")
        var createdOn: Date? = null,
        @Column(name="updated_on")
        var updatedOn:Date? = null
)
```

## Repository
JpaRepositoryを利用してRepositoryクラスを作成します。Domaなどの選択肢もありましたが、なるべく生のSQLを書きたくなかったのでJpaRepositoryを利用することにしました。

```kotlin
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import com.example.User

@Repository
interface UserRepository: JpaRepository<User, Long> {}
```

## Service
`@Service`をつけてServiceクラスであることを定義します。 ~~`@Autowired`をコンストラクタの前に付けて、クラスで使用するRepositoryクラスがDIされるようにします。~~
Kotlinの場合は@Autowiredをつけなくても、コンストラクタ引数に Beanが指定されている場合は、自動的にコンストラクタインジェクションになります。

```kotlin
@Service
class UserService(private val userRepository: UserRepository) {

    fun findById(id: Long): User {
        return userRepository.getOne(id)
    }

    fun findAll(): MutableList<User> {
        return userRepository.findAll()
    }

    fun register(user: User): User {
        return userRepository.save(user)
    }
}
```

## Applicationクラス
`@SpringBootApplication`を付けたクラスを用意します。

```kotlin
@SpringBootApplication
open class Application {
    companion object {
        @JvmStatic fun main(args: Array<String>) {
            SpringApplication.run(Application::class.java, *args)
        }
    }
}
```

このApplicationクラスを配置することで、配置したパッケージ以下がコンポーネントスキャン対象になり、`@Autowired`が使えるようになります。パッケージ対象外をコンポーネントスキャンしたい場合は`@ComponentScan([package])`とする必要があるみたいです。

## Controllerクラス
今回はAndroidアプリで使用するAPIサーバを想定しているので、レスポンスはJsonで返すことにします。
`@RestController`をクラス名に付与することで、各メソッドはレスポンスをJsonとして返すようになります。
`@Controller`というアノテーションもありますが、そちらは通常のViewを必要とするWebアプリケーションで使用しましょう。

```kotlin
@RestController
@RequestMapping(path= arrayOf("/"))
class UserController(private val userService: UserService) {
    fun index(): List<User> {
        return userService.findAll()
    }

    @RequestMapping(path= arrayOf("{id}"), method= arrayOf(RequestMethod.GET))
    fun show(@PathVariable id: Long): User {
        return userService.findById(id)
    }

    @RequestMapping(path= arrayOf("/"), method= arrayOf(RequestMethod.POST), params = arrayOf("name", "password"))
    @ResponseStatus(HttpStatus.CREATED)
    fun create(@RequestParam name: String, @RequestParam password: String): User {
        val user = User(0, name, password, null, null)
        return userService.register(user)
    }
}
```

`@RequestMapping`のpathには対応するパスを指定します。pathには複数のパスを指定することができます。

## 起動するぞーーー！
下記のコマンドを実行し、ビルトインサーバを立ち上げます。
このタイミングで自動的にFlywayのマイグレーションが実行されます。

```bash
$ gradlew bootRun
```

https://localhost:8080 へアクセスするとユーザ一覧がJsonで返ってくることが確認できるはずです....!

# 所感
SpringでのMVC開発はかなり簡単だなーという印象です。
一つコントローラを追加して、実際に動作確認するまで10分はかからなかったですし...。
あと、コントローラ、サービス、リポジトリ、エンティティクラスのアノテーションが用意されているので、この処理はリポジトリで、あの処理はサービスで--と考えなくても役割分担ができてしまい実装がすごく楽でした。

# 参考文献&役に立った記事
* [CentOS 6&7/Fedora 20にOpenJDK 8環境を構築](https://qiita.com/ytkumasan/items/2098c6ca93eaa44746b0)
* [LinuxでJavaのバージョンを切り替える](http://www.yunabe.jp/tips/linux_default_java_version.html)
* [Working with the Command Line Compiler](https://kotlinlang.org/docs/tutorials/command-line.html)
* [初心者のためのSpring Framework入門](https://www.tuyano.com/index3?id=12626003)
* [[SpringBoot] パッケージが異なるクラスを @Autowired する](https://dev.classmethod.jp/etc/springboot-componentscan/)