KotlinでJersey+JPAを使ったRESTサーバーを作る

今回は `Jersey`+`JPA` で REST API を `Kotlin` で書いてみます。


## 依存関係の設定
まず `gradle` はこんな感じです。

```build.gradle

buildscript {
    ext.kotlin_version = '1.2.31'
    ext.spring_version = '2.0.2.RELEASE'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

plugins {
    id 'java'
}

group 'com.example'
version '1.0-SNAPSHOT'

apply plugin: 'kotlin'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

    compile "org.springframework.boot:spring-boot-starter-jersey:$spring_version"
    compile "org.springframework.boot:spring-boot-starter-jdbc:$spring_version"
    compile "org.springframework.boot:spring-boot-starter-data-jpa:$spring_version"
    compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.5"
    compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.5"
    compile "mysql:mysql-connector-java:8.0.11"

    testCompile group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

```

## アプリケーションのエントリーポイント実装
つづいて、エントリーポイントの実装です。
設定がまとまってた方が見やすいかと思い `Jersery` の設定もここに書いちゃいました。
あまり薦められるやり方じゃないです。

```App.kt
@EnableAutoConfiguration
@ComponentScan
@Configuration
open class App

@Configuration
@ApplicationPath("/")
open class JerseyConfig : ResourceConfig() {
    init {
        packages("com.example")
        register(ContextResolver<ObjectMapper> { jacksonObjectMapper() })
    }
}


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


```

`register(ContextResolver<ObjectMapper> { jacksonObjectMapper() })` をすることで `JSON`文字列から Kotlin オブジェクトに変換できます。
`PUT`や`POST`で`JSON`を扱う場合は設定するといいです。

## レスポンスに使うEntityクラスの実装
続いて、レスポンスに使うデータクラスです。

```User.kt

data class User(
        val id: Int,
        @field:JsonProperty("名前") val name: String,
        @field:JsonProperty("歳") val age: Int,
        @field:JsonProperty("住所") val address: String?
)
```
`@field:JsonProperty` にある `field`ですが、アノテーションをどこに設定したいかを指定するものです。
Kotlinのプロパティは Java で言う所の `field` `getter` `setter` を兼ねていることから、この指定がないと Kotlin的にどこにつけるか困っちゃうみたいです。 
なので、`getter` につけたい場合は　`@get:JsonProperty`とします。

## REST API のエンドポイント実装
書くのに疲れてきました。。。
REST API部分の実装です。CRUD一通りあります。

```UserResource.kt

data class Parameter(
        @field:JsonProperty("名前")
        val name: String,

        @field:JsonProperty("歳")
        @field:Min(18)
        val age: Int,

        @field:JsonProperty("住所")
        val address: String
)

@Path("user")
class UserResource {

    @Inject
    lateinit var repo: UserRepository

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    fun all() = repo.getAll()

    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    fun getById(@PathParam("id") id: Int) = repo.get(id)

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    fun create(@Valid param: Parameter) = repo.create(param.name, param.age, param.address)

    @PUT
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    fun update(@PathParam("id") id: Int, @Valid param: Parameter) = repo.update(User(id, param.name, param.age?: 35, param.address))

    @DELETE
    @Path("{id}")
    fun delete(@PathParam("id") id : Int) = repo.delete(id)
    
```

## DBアクセス部分の実装
次はDBからデータを取得する部分になりますが、
今回は Repositoryを挟んでREST APIの層とDB層を分離させています。
これを挟むことで、O/Rマッパーは別のものに変えてもResouceに影響がでないようにするという考えです。
[実際にExposedに差し替えたパターンはこちらにあります。](https://qiita.com/nijuya_o/items/d1681b886fafc96f3525)

### JPAで実装
ここからはJPAを利用してDBからデータを取得し、DBに依存しないデータ形式に変換して返すという実装です。


```UserRepository.kt
interface UserRepository {
    fun get(id: Int): User
    fun getAll(): List<User>
    fun delete(id: Int)
    fun create(name: String, age: Int, address: String): User
    fun update(user: User): User
}
```

テーブルとマッピングするためのクラスです。

```UserModel.kt
@Entity
@Table(name = "user")
data class UserModel(
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Int = 0,
        var name: String = "",
        var age: Int = 0,
        var address: String = ""
)
```



```UserRepositoryImpl.kt
fun UserModel.toEntity() = User(id = id, name = name, address = address, age = age)

@Service
class UserRepositoryImpl : UserRepository {
    @Inject
    lateinit var repo: internalRepository

    override fun get(id: Int) = repo.findById(id).map { it.toEntity() }.orElseGet { throw NotFoundException() }

    override fun getAll() = repo.findAll().map { it.toEntity() }

    override fun delete(id: Int) = repo.deleteById(id)

    override fun create(name: String, age: Int, address: String)
            = repo.save(UserModel(name = name, age = age, address = address)).toEntity()

    override fun update(user: User)
            = repo.save(UserModel(id = user.id, name = user.name, age = user.age, address = user.address?: "不定")).toEntity()
}


@Repository
interface internalRepository : JpaRepository<UserModel, Int>
```

JPAが関係しているのはこれだけです。
```
@Repository
interface internalRepository : JpaRepository<UserModel, Int>
```

あとはDBから取得した`UserModel`を`User`に変換しているだけです。
`UserModel`も`User`もフィールドが同じなので、作る意味あるのかいって突っ込まれそうですがそこは意図を汲み取ってくれると助かります。

見やすさを意識して改行こそしてますが、処理自体はほとんど1行で書けちゃいました。