Kotlin
spring-boot
Flyway

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

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

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

アプリケーション作成

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

Spring Bootのインストール

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

SPRING INITIALIZR

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に接続できる状態にしておきます。

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に論理削除の状態を指定します。

@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を利用することにしました。

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されるようにします。

@Service
class UserService @Autowired constructor(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を付けたクラスを用意します。

@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アプリケーションで使用しましょう。

@RestController
@RequestMapping(path= arrayOf("/"))
class UserController @Autowired constructor(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のマイグレーションが実行されます。

$ gradlew bootRun

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

所感

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

参考文献&役に立った記事