サーバーサイドKotlin (Spring Boot / Doma 2) 入門

この記事は [エムスリー Advent Calendar 2017](https://qiita.com/advent-calendar/2017/m3) の18日目の記事です。

今年はKotlinが熱い1年でした！[今年の5月に、Googleがandroid開発言語としてKotlinを公式にサポートするとアナウンス](https://blog.jetbrains.com/jp/2017/05/18/767)してから、急速に利用が広がっているようですね:arrow_heading_up:

![KotlinAdoption.png](https://qiita-image-store.s3.amazonaws.com/0/4506/873bef18-159b-f622-fb92-a2a8bcb8a23a.png)

出典：https://blog.jetbrains.com/jp/2017/11/29/828

私が所属するエムスリーでも

- 日本Kotlinユーザグループ代表、[長澤太郎](https://twitter.com/ngsw_taro)が書籍を2冊出版
    - [Kotlin Webアプリケーション 新しいサーバサイドプログラミング](https://www.amazon.co.jp/Kotlin-Web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3-%E6%96%B0%E3%81%97%E3%81%84%E3%82%B5%E3%83%BC%E3%83%90%E3%82%B5%E3%82%A4%E3%83%89%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%95%B7%E6%BE%A4-%E5%A4%AA%E9%83%8E/dp/4865940669)
    - [Kotlinイン・アクション（共同翻訳）](https://www.amazon.co.jp/Kotlin%E3%82%A4%E3%83%B3%E3%83%BB%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3-Dmitry-Jemerov/dp/4839961743)
- イベント[『どこでもKotlin』](http://maeharin.hatenablog.com/entry/2017/08/29/105858)シリーズを開催
- 私のチームで[サーバーサイドKotlinによるシステムリニューアル](https://speakerdeck.com/maeharin/10nian-qian-falseregasisisutemuwosabasaidokotlindehururiniyuarusiteiruhua-number-jjug-ccc-number-ccc-g2)を実施

とKotlinに関する話題が盛り沢山の年となりました:sunny:

KotlinはJetBrains社が開発したいわゆるJVM言語です。型推論・Null Safety・便利な標準リスト操作関数などモダンな開発言語の特徴を備えつつ、既存のJavaコードともシームレスに相互運用できることが特徴です。Javaの有名なWebアプリケーションフレームワークであるSpring Bootも特に問題なく使うことができます。

この記事では、KotlinをSpring BootとDoma2（Java製のDBアクセスライブラリ）とともに使って、簡単なCRUDアプリケーションを立ち上げる手順を紹介します。まだサーバーサイドKotlinに触れたことが無い方は是非この機会に触ってみてもらえると嬉しいです！なお、[本記事で作るサンプルのソースコードはgithubにあげています](https://github.com/maeharin/kotlin-spring-boot-doma-demo)

> エディタ: IntelliJ IDEA
> Kotlin 1.1.61, Spring Boot 1.5.9, Doma 2.19.0
> * Kotlinは2017/12/16現在、spring initializrでDLしたバージョン

## Hello World! （たった3ステップ！）

まずはhello!と表示するREST APIを作ってみます。spring initializrを使うと、とても簡単です！

### 1. spring initializrからひな形をダウンロード

以下のサイトにアクセスし、所定の項目を選択した上で、「Generate Project」ボタンを押してzipをダウンロードしてください。

https://start.spring.io

- Gradle、Kotlin、Spring Boot 1.5.9
- Project Metadata: 好みに合わせて入力ください
- Dependencies: Web、DevTools、Thymeleaf

<img width="700" alt="スクリーンショット 2017-12-17 22.33.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/5b505b9c-f3f6-b233-fb51-d16cc75efbac.png">

### 2. controllerを作る

ダウンロードしたzipを解凍。IntelliJ IDEAでOpenを選択し、解凍したディレクトリを選択します

<img width="299" alt="スクリーンショット 2017-12-17 22.57.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/e5752ba4-a399-5d1c-f782-fd52fff430e2.png">


左メニューのファイルを作成したいフォルダにカーソルをあわせ、右クリック => New => Kotlin File/Classと選択します

<img width="325" alt="スクリーンショット 2017-12-17 22.41.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/19eaa4c6-85e5-a6a8-37cf-a0d85d78f58c.png">


NameをHomeController。KindをClassにしてOK

<img width="372" alt="スクリーンショット 2017-12-18 11.48.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/81a60220-8fdb-ef8b-a9f3-ebe5d77a91b0.png">



hello!と返すRestControllerを作成します。`@RestController`や`@GetMapping`はspringframeworkが提供するアノテーションです

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/HomeController.kt
package com.example.kotlinspringbootdomademo

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HomeController {
    @GetMapping("")
    fun index(): String = "hello!"
}
```

### 3. 起動コマンドを叩く

```
$ ./gradlew clean bootRun
```

http://localhost:8080 にアクセスすればhello!と表示されます！



## Thymeleafを使って、home画面を作成

今度はh1タグにhello Kotlin!と表示するhome画面を作成していきます。なお、htmlテンプレートにはThymeleafを使います

まずは、htmlを作成します。src/main/resources/templates/home.htmlというファイルを作成し、messageという変数をh1に展開するようにしておきます

```html:src/main/resources/templates/home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
</head>
<body>
    <h1 th:text="${message}"></h1>
</body>
</html>
```

次に、先程作ったHomeControllerを修正します

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/HomeController.kt
+import org.springframework.stereotype.Controller
+import org.springframework.ui.Model
 import org.springframework.web.bind.annotation.GetMapping
-import org.springframework.web.bind.annotation.RestController

-@RestController
+@Controller
 class HomeController {
     @GetMapping("")
-    fun index(): String = "hello!"
+    fun index(model: Model): String {
+        model.addAttribute("message", "hello Kotlin!")
+        return "home"
+    }
```

ビルドしなおして、 http://localhost:8080 にアクセスすると、h1タグにhello Kotlin!と表示されます！

<img width="250" alt="スクリーンショット 2017-12-17 23.19.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/dae7fbda-2cb9-2ba8-4863-f81a1866524f.png">

ついでに、CSSも使って背景を赤くしてみましょう。cssファイルを作成します

```css:src/main/resources/static/css/app.css
body {
    background-color: red;
}
```

作成したcssをhtmlから読み込みます

```diff:src/main/resources/templates/home.html
 <head>
     <meta charset="UTF-8" />
     <title>Title</title>
+    <link th:href="@{/css/app.css}" rel="stylesheet" />
 </head>
```

背景が赤くなればOKです

<img width="250" alt="スクリーンショット 2017-12-17 23.20.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/7f13564e-005c-c29b-74ab-b5624d7988e5.png">

## DB（Postgresql、Docker）の準備

さて、ここからはDBアクセスです。インメモリDBのH2を使ってもよいのですが、今回はDockerでpostgresqlを立てることにします。以下のファイルを作成してください

```yaml:docker-compose.yml
version: "3"
services:
  postgres:
    build: ./docker/postgres/
    image: kotlin-demo-postgres
    ports:
      - 5432:5432
    container_name: kotlin-demo-postgres-container
```

```dockerfile:docker/postgres/Dockerfile
FROM postgres:9.5.3

COPY ./docker-entrypoint-initdb.d /docker-entrypoint-initdb.d
```

```bash:docker/postgres/docker-entrypoint-initdb.d/init-db.sh
#!/bin/bash
set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
    CREATE DATABASE kotlindemo;
EOSQL

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" kotlindemo <<-EOSQL
    BEGIN;

    create table customer (
        id serial,
        name varchar(50),
        email varchar(50)
    );

    insert into customer(name, email) values ('hoge', 'hoge@example.com');
    insert into customer(name, email) values ('fuge', 'fuge@example.com');
    insert into customer(name, email) values ('piyo', 'piyo@example.com');

    COMMIT;
EOSQL
```

起動します

```
$ docker-machine start
$ eval $(docker-machine env default)
$ docker-compose up
```

コンテナにアクセスして、初期データが入っていることを確認できればOKです

```
$ eval $(docker-machine env default)
$ docker exec -it kotlin-demo-postgres-container psql -U postgres kotlindemo

kotlindemo=# select * from customer;
 id | name |      email
----+------+------------------
  1 | hoge | hoge@example.com
  2 | fuge | fuge@example.com
  3 | piyo | piyo@example.com
(3 rows)
```

## Doma 2でDBアクセス

DBアクセスには、Doma 2というライブラリを使います。Java製のライブラリで、2-way SQL（SQLファイルを外部ファイル化できる）のが特徴です（詳しくは[公式のドキュメント](http://doma.readthedocs.io/ja/stable/)をご覧ください）。ちなみに、私達のチームでは、[Domaの開発で大切にしている10のこと](https://qiita.com/nakamura-to/items/099cf72f5465d0323521)という開発方針がよいと思ったのと、社内のJavaプロジェクトで導入実績があったことからDoma 2を採用しました。

なお、[Doma2はKotlin1.1.2を実験的にサポートしている](http://doma.readthedocs.io/ja/stable/kotlin-support/)のですが、私のチームでは、将来Kotlinのバージョンが上がっていった際にハマるリスクを回避するために「[Domaの層は全てJavaで書く](http://doma.readthedocs.io/ja/stable/kotlin-support/#javakotlin)」という選択をしています（本記事で紹介するようにDomain層でKotlinにマッピングすれば、実際に触るコードは殆どKotlinになります）。今回も同じように「Domaの層は全てJavaで書く」という選択をとります

まずは依存ライブラリを入れます。`doma-spring-boot-starter`を使うことで、Spring BootへのDomaの導入が簡単になります。

```diff:build.gradle
        ext {
                kotlinVersion = '1.1.61'
                springBootVersion = '1.5.9.RELEASE'
+               postgresqlVersion = '42.1.4'
+               springBootDomaVersion = '1.1.1'
+               domaVersion = '2.19.0'
        }
        repositories {
                mavenCentral()
@@ -32,6 +35,12 @@ repositories {
        mavenCentral()
 }

+// -------------------------------------------
+// for Doma2 with gradle
+// see: http://doma.readthedocs.io/ja/stable/build/#gradle
+// -------------------------------------------
+processResources.destinationDir = compileJava.destinationDir
+compileJava.dependsOn processResources

 dependencies {
        compile('org.springframework.boot:spring-boot-starter-thymeleaf')
@@ -40,4 +49,15 @@ dependencies {
        compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
        runtime('org.springframework.boot:spring-boot-devtools')
        testCompile('org.springframework.boot:spring-boot-starter-test')
+
+       // -------------------------------------------
+       // postgresql
+       // -------------------------------------------
+       compile "org.postgresql:postgresql:${postgresqlVersion}"
+
+       // -------------------------------------------
+       // Doma2
+       // -------------------------------------------
+       compile "org.seasar.doma.boot:doma-spring-boot-starter:${springBootDomaVersion}"
+       compile "org.seasar.doma:doma:${domaVersion}"
 }
```

application.ymlにdomaがpostgresを使うことを記載します。（application.propertiesはyml形式にしたいので、ファイル名を変更しておきます）

```
$ mv src/main/resources/application.properties src/main/resources/application.yml
```

```yaml:src/main/resources/application.yml
doma:
  dialect: postgres
```

DomaのEntity（Java）を作成します。

```java:src/main/java/com/example/kotlinspringbootdomademo/infrastructure/doma/entity/CustomerDomaEntity.java
package com.example.kotlinspringbootdomademo.infrastructure.doma.entity;

import org.seasar.doma.*;
import org.seasar.doma.jdbc.entity.NamingType;

@Entity(naming = NamingType.SNAKE_UPPER_CASE)
@Table(name = "customer")
public class CustomerDomaEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer id;

    public String name;

    public String email;
}
```

DomaのDaoインターフェース（Java）を作成します

```java:src/main/java/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao.java
package com.example.kotlinspringbootdomademo.infrastructure.doma.dao;

import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity;
import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;

import java.util.List;

@ConfigAutowireable
@Dao
public interface CustomerDomaDao {
    @Select
    List<CustomerDomaEntity> selectAll();
}
```

SQLファイルを作成します

```sql:src/main/resources/META-INF/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao/selectAll.sql
select
    /*%expand*/*
from
    customer
order by
    id desc
```

ここまででがDomaのコード（Javaで書く部分）となります。補足ですが、既存DBが数100テーブルある場合、DomaのEntity、Dao、SQLを手動で作成していくのは骨の折れる作業です。[Doma-Gen](http://doma-gen.readthedocs.io/ja/stable/)を使うとこれらを自動生成することができます。

さて、ここからはKotlinです

ドメインのmodelを作成します

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/domain/model/Customer.kt
package com.example.kotlinspringbootdomademo.domain.model

data class Customer(val id: Int? = null, val name: String, val email: String)
```

Repositoryのインターフェースを作成します

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/domain/repository/CustomerRepository.kt
package com.example.kotlinspringbootdomademo.domain.repository

import com.example.kotlinspringbootdomademo.domain.model.Customer

interface CustomerRepository {
    fun findAll(): List<Customer>
}
```

Repositoryの実装を作成します。ここでDomaのEntity（Java）をドメインのModel（Kotlin）に詰め替えます

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/infrastructure/domarepository/CustomerRepositoryDomaImpl.kt
package com.example.kotlinspringbootdomademo.infrastructure.domarepository

import com.example.kotlinspringbootdomademo.domain.model.Customer
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import com.example.kotlinspringbootdomademo.infrastructure.doma.dao.CustomerDomaDao
import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity
import org.springframework.stereotype.Repository

@Repository
class CustomerRepositoryDomaImpl(
        private val customerDomaDao: CustomerDomaDao
): CustomerRepository {
    override fun findAll(): List<Customer> {
        return customerDomaDao.selectAll().map { _mapToModel(it) }
    }

    // ここでDomaのEntity（Java）をドメインのModel（Kotlin）に詰め替える
    private fun _mapToModel(domaEntity: CustomerDomaEntity): Customer {
        return Customer(
                id = domaEntity.id,
                name = domaEntity.name,
                email = domaEntity.email
        )
    }
}
```

Controllerを作成します

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/application/controller/web/CustomerController.kt
package com.example.kotlinspringbootdomademo.application.controller.web

import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping

@Controller
@RequestMapping("/customers")
class CustomerController(
        private val customerRepository: CustomerRepository
) {
    @GetMapping("")
    fun index(model: Model): String {
        val customers = customerRepository.findAll()
        model.addAttribute("customers", customers)
        return "customers/index"
    }
}
```

htmlを作成します

```html:src/main/resources/templates/customers/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
    <h1>顧客一覧</h1>
    <ul th:unless="${customers.isEmpty()}">
        <li th:each="customer: ${customers}">
            <span th:text="${customer.id}"></span>
            <span th:text="${customer.name}"></span>
            <span th:text="${customer.email}"></span>
        </li>
    </ul>
</body>
</html>
```

最後にSpring Bootがデータベースに接続するための環境変数を設定

```bash:.env.dev
DOCKER_IP=$(docker-machine ip)

export SPRING_DATASOURCE_URL=jdbc:postgresql://${DOCKER_IP}:5432/kotlindemo
export SPRING_DATASOURCE_USERNAME=postgres
export SPRING_DATASOURCE_PASSWORD=
export SPRING_DATASOURCE_DRIVER_CLASS_NAME="org.postgresql.Driver"
```

環境変数を読み込んだうえで、起動します

```
$ source .env.dev
$ ./gradlew clean bootRun
```

http://localhost:8080/customers にアクセスしてDBの中身が表示されればOKです！

<img width="364" alt="スクリーンショット 2017-12-18 1.23.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/30f4053c-999e-8728-6024-b735055fcdc6.png">

## 詳細ページ

一覧ページを作成することができたので、今度は詳細ページを作成していきます

domaのDaoインターフェース

```java:src/main/java/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao.java
 public interface CustomerDomaDao {
     @Select
     List<CustomerDomaEntity> selectAll();
+
+    @Select
+    CustomerDomaEntity selectById(int id);
 }
```

domaのSQL

```sql:src/main/resources/META-INF/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao/selectById.sql
select
    /*%expand*/*
from
    customer
where
    id = /* id */1
```

repository（インターフェース）

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/domain/repository/CustomerRepository.kt
 interface CustomerRepository {
     fun findAll(): List<Customer>
+    fun findById(id: Int): Customer?
```

repository（実装）

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/infrastructure/domarepository/CustomerRepositoryDomaImpl.kt
+    override fun findById(id: Int): Customer? {
+        return customerDomaDao.selectById(id)?.let { _mapToModel(it) }
+    }
```

controller

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/application/controller/web/CustomerController.kt

+import com.example.kotlinspringbootdomademo.application.RecordNotFoundException
 import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
 import org.springframework.stereotype.Controller
 import org.springframework.ui.Model
 import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
 import org.springframework.web.bind.annotation.RequestMapping

 @Controller
@@ -17,4 +19,14 @@ class CustomerController(
         model.addAttribute("customers", customers)
         return "customers/index"
     }
+
+    @GetMapping("{id}")
+    fun show(
+            @PathVariable id: Int,
+            model: Model
+    ): String {
+        val customer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+        model.addAttribute("customer", customer)
+        return "customers/show"
+    }
```

idがなかった場合のExceptionを追加

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/application/exceptions.kt
package com.example.kotlinspringbootdomademo.application

import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@ResponseStatus(HttpStatus.NOT_FOUND)
class RecordNotFoundException(): RuntimeException()
```

htmlを作成

```html:src/main/resources/templates/customers/show.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
    <h1 th:text="${customer.name}"></h1>
    <span th:text="${customer.email}"></span>
</body>
</html>
```

http://localhost:8080/customers/1 にアクセスして詳細情報が表示されればOKです

<img width="323" alt="スクリーンショット 2017-12-18 2.07.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/82aede5e-f4a3-0922-7ab1-912b36d61683.png">


## 作成ページ

このまま作成ページも作成してみましょう。

domaのdaoインターフェース

```diff:src/main/java/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao.java
@@ -1,7 +1,9 @@
 package com.example.kotlinspringbootdomademo.infrastructure.doma.dao;

 import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity;
 import org.seasar.doma.Dao;
+import org.seasar.doma.Insert;
 import org.seasar.doma.Select;
 import org.seasar.doma.boot.ConfigAutowireable;

@@ -15,4 +17,7 @@ public interface CustomerDomaDao {

     @Select
     CustomerDomaEntity selectById(int id);
+
+    @Insert
+    int insert(CustomerDomaEntity entity);
 }
```

repository（インターフェース）

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/domain/repository/CustomerRepository.kt
 interface CustomerRepository {
     fun findAll(): List<Customer>
     fun findById(id: Int): Customer?
+    fun create(customer: Customer): Int
 }
```

repository（実装）。ここでドメインのModel（Kotlin）をDomaのEntity（Java）をに詰め替える

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/infrastructure/domarepository/CustomerRepositoryDomaImpl.kt
@@ -18,6 +18,12 @@ class CustomerRepositoryDomaImpl(
         return customerDomaDao.selectById(id)?.let { _mapToModel(it) }
     }

+    override fun create(customer: Customer): Int {
+        val domaEntity = _mapToDomaEntity(customer)
+        customerDomaDao.insert(domaEntity)
+        return domaEntity.id
+    }
+
     private fun _mapToModel(domaEntity: CustomerDomaEntity): Customer {
         return Customer(
                 id = domaEntity.id,
@@ -25,4 +31,12 @@ class CustomerRepositoryDomaImpl(
                 email = domaEntity.email
         )
     }
+
+    // ここでドメインのModel（Kotlin）をDomaのEntity（Java）をに詰め替える
+    private fun _mapToDomaEntity(customer: Customer): CustomerDomaEntity {
+        return CustomerDomaEntity().also {
+            it.id = customer.id
+            it.name = customer.name
+            it.email = customer.email
+        }
+    }
```

applicationservice層を追加（`@Transactional`でトランザクション境界を宣言）

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/application/service/CustomerApplicationService.kt
package com.example.kotlinspringbootdomademo.application.service

import com.example.kotlinspringbootdomademo.application.input.CustomerInput
import com.example.kotlinspringbootdomademo.domain.model.Customer
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
class CustomerApplicationService(
        private val customerRepository: CustomerRepository
) {
    fun create(customerInput: CustomerInput): Int {
        val customer = Customer(
                name = customerInput.name!!,
                email = customerInput.email!!
        )

        return customerRepository.create(customer)
    }
}
```

ユーザーの入力を表現するクラスを作成

```kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/application/input/CustomerInput.kt
package com.example.kotlinspringbootdomademo.application.input

import org.hibernate.validator.constraints.NotBlank
import javax.validation.constraints.Size

class CustomerInput {
    @NotBlank
    @Size(max = 20)
    var name: String? = null

    @NotBlank
    @Size(max = 50)
    var email: String? = null
}
```

controller

```diff:kotlin:src/main/kotlin/com/example/kotlinspringbootdomademo/application/controller/web/CustomerController.kt
+import com.example.kotlinspringbootdomademo.application.input.CustomerInput
+import com.example.kotlinspringbootdomademo.application.service.CustomerApplicationService
 import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository

class CustomerController(
-        private val customerRepository: CustomerRepository
+        private val customerRepository: CustomerRepository,
+        private val customerApplicationService: CustomerApplicationService
) {

+    @GetMapping("new")
+    fun new(input: CustomerInput): String {
+        return "customers/new"
+    }
+
+    @PostMapping("")
+    fun create(
+            @Validated customerInput: CustomerInput,
+            bindingResult: BindingResult
+    ): String {
+        if(bindingResult.hasErrors()) {
+            return "customers/new"
+        }
+
+        val id = customerApplicationService.create(customerInput)
+
+        return "redirect:/customers/${id}"
+    }
```



htmlを作成

```html:src/main/resources/templates/customers/new.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
    <h1>顧客作成</h1>
    <form th:method="post" th:action="@{./}" th:object="${customerInput}">
        氏名: <input type="text" th:field="*{name}" />
        <p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>

        email: <input type="text" th:field="*{email}" />
        <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>

        <input type="submit" value="作成" />
    </form>
</body>
</html>
```

http://localhost:8080/customers/new にアクセス

<img width="421" alt="スクリーンショット 2017-12-18 2.15.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/defc0b44-77ed-1463-7675-477e085e7c94.png">

間違った入力をして「作成」ボタンを押すとエラーメッセージが表示されます

<img width="324" alt="スクリーンショット 2017-12-18 2.44.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/db480c55-2259-1d41-b9d1-5935edefdc46.png">

正しい値を入力して「作成」を押すと、データが作成されている様子を確認できればOKです

## 編集ページ

最後に編集ページを作成します

domaのdaoインターフェース

```diff:src/main/java/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao.java
@@ -4,6 +4,7 @@ import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerD
 import org.seasar.doma.Dao;
 import org.seasar.doma.Insert;
 import org.seasar.doma.Select;
+import org.seasar.doma.Update;
 import org.seasar.doma.boot.ConfigAutowireable;

 import java.util.List;
@@ -19,4 +20,7 @@ public interface CustomerDomaDao {

     @Insert
     int insert(CustomerDomaEntity entity);
+
+    @Update
+    int update(CustomerDomaEntity entity);
 }
```

repositoryインターフェース

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/domain/repository/CustomerRepository.kt
@@ -6,4 +6,5 @@ interface CustomerRepository {
     fun findAll(): List<Customer>
     fun findById(id: Int): Customer?
     fun create(customer: Customer): Int
+    fun update(customer: Customer)
```

repository実装

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/infrastructure/domarepository/CustomerRepositoryDomaImpl.kt
@@ -24,6 +24,11 @@ class CustomerRepositoryDomaImpl(
         return domaEntity.id
     }

+    override fun update(customer: Customer) {
+        val domaEntity = _mapToDomaEntity(customer)
+        customerDomaDao.update(domaEntity)
+    }
```

applicationserviceを修正

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/application/service/CustomerApplicationService.kt
@@ -1,5 +1,6 @@
 package com.example.kotlinspringbootdomademo.application.service

+import com.example.kotlinspringbootdomademo.application.RecordNotFoundException
 import com.example.kotlinspringbootdomademo.application.input.CustomerInput
 import com.example.kotlinspringbootdomademo.domain.model.Customer
 import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
@@ -11,6 +12,14 @@ import org.springframework.transaction.annotation.Transactional
 class CustomerApplicationService(
         private val customerRepository: CustomerRepository
 ) {
+    fun findAll(): List<Customer> {
+        return customerRepository.findAll()
+    }
+
+    fun findById(id: Int): Customer {
+        return customerRepository.findById(id) ?: throw RecordNotFoundException()
+    }
+
     fun create(customerInput: CustomerInput): Int {
         val customer = Customer(
                 name = customerInput.name!!,
@@ -19,4 +28,15 @@ class CustomerApplicationService(

         return customerRepository.create(customer)
     }
+
+    fun update(id: Int, customerInput: CustomerInput) {
+        val existingCustomer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+
+        val customer = existingCustomer.copy(
+                name = customerInput.name!!,
+                email = customerInput.email!!
+        )
+
+        customerRepository.update(customer)
+    }
 }
```


controller（repositoryを直接触るのはやめて、applicationserviceを介すように統一しています）

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/application/controller/web/CustomerController.kt
@@ -1,27 +1,21 @@
 package com.example.kotlinspringbootdomademo.application.controller.web

-import com.example.kotlinspringbootdomademo.application.RecordNotFoundException
 import com.example.kotlinspringbootdomademo.application.input.CustomerInput
 import com.example.kotlinspringbootdomademo.application.service.CustomerApplicationService
-import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
 import org.springframework.stereotype.Controller
 import org.springframework.ui.Model
 import org.springframework.validation.BindingResult
 import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.GetMapping
-import org.springframework.web.bind.annotation.PathVariable
-import org.springframework.web.bind.annotation.PostMapping
-import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.*

 @Controller
 @RequestMapping("/customers")
 class CustomerController(
-        private val customerRepository: CustomerRepository,
         private val customerApplicationService: CustomerApplicationService
 ) {
     @GetMapping("")
     fun index(model: Model): String {
-        val customers = customerRepository.findAll()
+        val customers = customerApplicationService.findAll()
         model.addAttribute("customers", customers)
         return "customers/index"
     }
@@ -31,7 +25,7 @@ class CustomerController(
             @PathVariable id: Int,
             model: Model
     ): String {
-        val customer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+        val customer = customerApplicationService.findById(id)
         model.addAttribute("customer", customer)
         return "customers/show"
     }
@@ -54,4 +48,32 @@ class CustomerController(

         return "redirect:/customers/${id}"
     }
+
+    @GetMapping("{id}/edit")
+    fun edit(
+            @PathVariable id: Int,
+            customerInput: CustomerInput
+    ): String {
+        val customer = customerApplicationService.findById(id)
+
+        customerInput.name = customer.name
+        customerInput.email = customer.email
+
+        return "customers/edit"
+    }
+
+    @PatchMapping("{id}")
+    fun update(
+            @PathVariable id: Int,
+            @Validated customerInput: CustomerInput,
+            bindingResult: BindingResult
+    ): String {
+        if(bindingResult.hasErrors()) {
+            return "customers/edit"
+        }
+
+        customerApplicationService.update(id, customerInput)
+
+        return "redirect:/customers"
+    }
```





htmlファイル

```html:src/main/resources/templates/customers/edit.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
    <h1>顧客編集</h1>
    <form th:method="patch" th:action="@{./}" th:object="${customerInput}">
        氏名: <input type="text" th:field="*{name}" />
        <p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>

        email: <input type="text" th:field="*{email}" />
        <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>

        <input type="submit" value="編集" />
    </form>
</body>
</html>
```

http://localhost:8080/customers/1/edit にアクセス

<img width="422" alt="スクリーンショット 2017-12-18 3.02.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/68043dab-a5a1-236a-1430-2f0f600393b8.png">

こちらも不正な値を入れるとエラーメッセージが表示されます

<img width="362" alt="スクリーンショット 2017-12-18 3.02.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/ec614bd8-cc03-efda-3186-5d4e2f1a8e1b.png">

正しい値を入力して「編集」を押すと、データが編集されている様子を確認できればOKです

## 削除

最後に削除を実装して終わりです

Domaのdaoインターフェース

```diff:src/main/java/com/example/kotlinspringbootdomademo/infrastructure/doma/dao/CustomerDomaDao.java
@@ -1,10 +1,7 @@
 package com.example.kotlinspringbootdomademo.infrastructure.doma.dao;

 import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity;
-import org.seasar.doma.Dao;
-import org.seasar.doma.Insert;
-import org.seasar.doma.Select;
-import org.seasar.doma.Update;
+import org.seasar.doma.*;
 import org.seasar.doma.boot.ConfigAutowireable;

 import java.util.List;
@@ -23,4 +20,7 @@ public interface CustomerDomaDao {

     @Update
     int update(CustomerDomaEntity entity);
+
+    @Delete
+    int delete(CustomerDomaEntity entity);
 }
```

repositoryインターフェース

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/domain/repository/CustomerRepository.kt
@@ -7,4 +7,5 @@ interface CustomerRepository {
     fun findById(id: Int): Customer?
     fun create(customer: Customer): Int
     fun update(customer: Customer)
+    fun delete(customer: Customer)
 }
```

repository実装

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/infrastructure/domarepository/CustomerRepositoryDomaImpl.kt
@@ -29,6 +29,11 @@ class CustomerRepositoryDomaImpl(
         customerDomaDao.update(domaEntity)
     }

+    override fun delete(customer: Customer) {
+        val domaEntity = _mapToDomaEntity(customer)
+        customerDomaDao.delete(domaEntity)
+    }
```

applicationservice

```diff:rc/main/kotlin/com/example/kotlinspringbootdomademo/application/service/CustomerApplicationService.kt
@@ -39,4 +39,9 @@ class CustomerApplicationService(

         customerRepository.update(customer)
     }
+
+    fun delete(id: Int) {
+        val customer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+        customerRepository.delete(customer)
+    }
```

controller

```diff:src/main/kotlin/com/example/kotlinspringbootdomademo/application/controller/web/CustomerController.kt
@@ -76,4 +76,12 @@ class CustomerController(

         return "redirect:/customers"
     }
+
+    @DeleteMapping("{id}")
+    fun delete(
+            @PathVariable id: Int
+    ): String {
+        customerApplicationService.delete(id)
+        return "redirect:/customers"
+    }
```

html


```diff:src/main/resources/templates/customers/index.html
@@ -12,6 +12,9 @@
             <span th:text="${customer.id}"></span>
             <span th:text="${customer.name}"></span>
             <span th:text="${customer.email}"></span>
+            <form th:object="${customer}" th:action="@{/customers/__${customer.id}__}" th:method="delete">
+                <input type="submit" value="削除" />
+            </form>
         </li>
     </ul>
 </body>
```

削除ボタンを押して該当レコードが削除されればOKです

<img width="347" alt="スクリーンショット 2017-12-18 11.39.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/4506/ec0b082d-d716-cca9-3d7f-15d827eb154e.png">


## まとめ

いかがでしたでしょうか。このように、Kotlinはandroidだけでなく、サーバーサイドでも使える言語です。既存のJavaライブラリもシームレスに使えますので、是非一度触ってみてください！have a nice Kotlin!