$30 off During Our Annual Pro Sale. View Details »

KotlinとSpring BootとDoma2でAPIサーバーを作る #m3kt

Hidenori Maehara
September 28, 2017

KotlinとSpring BootとDoma2でAPIサーバーを作る #m3kt

どこでもKotlin #2 サーバーサイドKotlin特集での発表資料です
サンプルコードgithubにあげてます
https://github.com/maeharin/kotlin-dvd-rental

Hidenori Maehara

September 28, 2017
Tweet

More Decks by Hidenori Maehara

Other Decks in Technology

Transcript

  1. Kotlinと
    Spring Bootと
    Doma2で
    APIサーバーを作る
    2017/9/28 どこでもKotlin #2
    エムスリー株式会社 前原
    @maeharin

    View Slide

  2. サンプルコード、githubにあげてます
    https://github.com/maeharin/kotlin-dvd-rental
    ● Kotlin x Spring Boot x Doma2で作ったAPIサーバーのサンプル
    ● DBにPostgreSQL、検索にElasticsearch(Dockerで起動)
    ● Postgresql tutorialのDVD Rentalサンプルデータベースを利用

    View Slide

  3. http://www.postgresqltutorial.com/postgresql-sample-database/

    View Slide

  4. 自己紹介
    ● 前原 秀徳
    ● @maeharin(まえはりん)
    ● エムスリー株式会社 エンジニア
    ● チームリーダー、グループ会社取締役等を歴任
    ● 自慢:ブログ記事が、はてぶ1200
    ○http://maeharin.hatenablog.com/

    View Slide

  5. エムスリーって何の会社?
    ・医療に関するWebサービスを多数展開
    ・全世界で約400万人の医師会員
    ・日本で約25万人の医師会員

    View Slide

  6. 今、私たちは10年に一度の
    システムリニューアルをしてます!
    ↓詳しくは前回の資料をご覧ください
    https://speakerdeck.com/maeharin/sisutemuriniyuaruto-sabasaidokotlin

    View Slide

  7. リニューアル後の技術スタック
    WEBアプリ
    APIサーバー
    スマホアプリ

    View Slide

  8. アジェンダ
    ● Kotlin x SpringBootでHello API
    ● Kotlin x Springの各種機能
    ● Kotlin x Jackson
    ● Kotlin x Doma2

    View Slide

  9. Kotlin x Spring Boot
    でHello API

    View Slide

  10. たった3ステップ
    ● 1) SPRING INITIALIZRからDLしたzipを解凍
    ● 2) KotlinでRestControllerを作成
    ● 3) ./gradlew bootRun

    View Slide

  11. 1) SPRING INITIALIZRからDLしたzipを解凍
    Kotlinを選ぶ
    https://start.spring.io

    View Slide

  12. 2) KotlinでRestControllerを作成
    package com.example.demo.controller
    import org.springframework.web.bind.annotation.*
    @RestController
    class HelloController {
    @GetMapping
    fun index():String = "hello!"
    }
    src/main/kotlin/com/example/demo/controller/HelloController.kt
    Kotlinで書く

    View Slide

  13. 3) ./gradlew bootRun
    $ ./gradlew bootRun

    View Slide

  14. 以上!

    View Slide

  15. Kotlin x Springの各種機能

    View Slide

  16. 基本的に、そのまま使える

    View Slide

  17. Kotlinのクラスはデフォルトでfinal。普通ならopenが必要
    だが...
    @RestController
    @RequestMapping("/api/v1/films")
    open class FilmRestController(
    都度openするの大変。。。

    View Slide

  18. kotli-springプラグインでopen不要に
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
    classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    apply plugin: 'kotlin'
    apply plugin: 'kotlin-spring'
    compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
    }
    compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
    }lin-reflect:${kotlinVersion}")
    これのお陰でopen不要に
    ※Spring Initializr経由で作成する
    とデフォルトで入る
    ↓Spring Initializr経由で作成したbuild.gradle(抜粋)

    View Slide

  19. controller
    @RestController
    @RequestMapping("/api/v1/films")
    class FilmRestController(
    private val filmRepository: FilmDomaRepository
    ) {
    @GetMapping
    fun index(): List = ...
    @PostMapping
    fun create(
    @RequestBody @Validated filmParam: FilmParam
    ): Int {
    open不要
    springの
    アノテーションは普
    通に使える

    View Slide

  20. @Service
    class FilmApplicationService(
    private val filmRepository: FilmDomaRepository
    ) {
    @Transactional
    fun create(command: FilmCommand): Int {
    @Repository
    class FilmDomaRepository(
    private val filmDao: FilmDao
    ) {
    DI

    View Slide

  21. @Configuration
    class ObjectMapperConfig {
    @Bean
    fun objectMapper(): ObjectMapper
    = ObjectMapper()
    .registerModule(JavaTimeModule())
    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
    .registerModule(KotlinModule())
    }
    Config

    View Slide

  22. SpringSecurity
    @Configuration
    @EnableAuthorizationServer
    class Oauth2AuthorizationServerConfig(
    private val authManager: AuthenticationManager,
    private val dataSource: DataSource
    ) : AuthorizationServerConfigurerAdapter() {
    @Bean
    fun passwordEncoder(): BCryptPasswordEncoder
    = BCryptPasswordEncoder()
    Spring周辺の
    ライブラリも
    問題なく使える

    View Slide

  23. SpringFox(Swagger)
    @Configuration
    @EnableSwagger2
    class SwaggerConfig {
    @Bean
    fun apiV1Document(): Docket
    = Docket(DocumentationType.SWAGGER_2)
    .groupName("api v1")
    .select()
    .paths(PathSelectors.ant("/api/v1/**"))
    .build()
    }

    View Slide

  24. View Slide

  25. Kotlin x Jackson

    View Slide

  26. data class FilmResource(
    var name: String = "",
    var isAdmin: Boolean = false,
    var companyId: Int = 0
    )
    APIへのリクエスト(Json)をKotlinへマッピング
    Jacksonは
    デフォルト値ありの
    コンストラクタを要

    デフォルト値は使い
    たくないのに。。

    View Slide

  27. こうしたい
    data class FilmResource(
    val name: String,
    val isAdmin: Boolean,
    val companyId: Int
    )

    View Slide

  28. jackson-module-kotlin

    View Slide

  29. 依存ライブラリに入れるだけ
    compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.0"
    ※Spring4.3より依存ライブラリに入れれば自動的に registerされるようになった
    https://spring.io/blog/2016/02/15/developing-spring-boot-applications-with-kotlin#jackson-kotlin-module

    View Slide

  30. val source = FilmSource(film)
    val json = objectMapper.writeValueAsString(source)
    val indexRequest = IndexRequest(INDEX, TYPE, id)
    .source(json, XContentType.JSON)
    ElasticSearchとJsonやり取りする時も嬉しい
    data class => json

    View Slide

  31. searchResponse.hits.map { hit ->
    val json = hit.sourceAsString
    val filmSource: FilmSource = objectMapper.readValue(json)
    }
    ElasticSearchとJsonやり取りする時も嬉しい
    json => data class

    View Slide

  32. Kotlin x Doma2

    View Slide

  33. Doma2とは
    ● JavaのDBアクセスフレームワーク。2Way-SQL(SQLを外出しできる)
    ● 社内のJavaプロジェクトではDoma2・MyBatisの利用事例が多い
    ● 私達のチームではDoma2を選択した
    ○社内に事例豊富。また、以下の開発方針を良いと思った。
    https://qiita.com/nakamura-to/items/099cf72f5465d0323521

    View Slide

  34. doma-spring-boot-starter
    ● spring boot向けのstarterが提供されている
    ● これを使うとより簡単に使いはじめることができる

    View Slide

  35. DomaはKotlin 1.1.2を実験的にサポート
    http://doma.readthedocs.io/ja/stable/kotlin-support/

    View Slide

  36. EntityをKotlinで
    http://doma.readthedocs.io/ja/stable/kotlin-support/

    View Slide

  37. DaoはJava
    http://doma.readthedocs.io/ja/stable/kotlin-support/

    View Slide

  38. kaptで注釈処理
    http://doma.readthedocs.io/ja/stable/kotlin-support/

    View Slide

  39. 以下のバージョンで試したところ、問題なく動いた
    ● Kotlin 1.1.2
    ● Doma 2.16.1

    View Slide

  40. ただ、バージョン上げると...
    ● Kotlin 1.1.4に上げるとエラー
    ● だが、compileKotlin.dependsOn processResourcesを指定すれ
    ば動いた
    ● しかし、Domaを2.17.0にあげるとエラー(原因は未調査)

    ● 回避策はあるかもしれないが、できればハマるリスクを少なく
    したい

    View Slide

  41. Domaの層は全てJavaで書く。という選択肢
    http://doma.readthedocs.io/ja/stable/kotlin-support/

    View Slide

  42. 私達の選択:DomaはJava。それ以外はKotlin
    ● よく触るのはDomaの層ではなく、それを使う層
    ● 「使う層」を全てKotlinにすれば、Kotlinの恩恵を十分受けられる
    ● Domaのコード(Java)はdoma-genという自動生成ツールを使って生成す
    ることも可能
    ○生成されるコードをコンフィグでカスタマイズ可能
    ○細かく制御したければ、テンプレートのカスタマイズも可能
    ● doma-genを使わないとしても、Domaのコードにロジックを書かなければ
    Javaのツラミはあまり感じない

    View Slide

  43. ディレクトリ例
    Java
    domaに関するコード
    のみ
    Kotlin
    上記以外全て
    SQL

    View Slide

  44. Javaの部分: DomaのDao
    @ConfigAutowireable
    @Dao
    public interface FilmEntityDao {
    @Select
    FilmEntity selectById(Integer filmId);
    @Select
    List selectAll();
    @Insert
    int insert(FilmEntity entity);

    View Slide

  45. Javaの部分: DomaのEntity
    @Entity(naming = NamingType.SNAKE_LOWER_CASE)
    @Table(name = "film")
    public class FilmEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(sequence = "film_film_id_seq")
    public Integer filmId;
    public String title;
    public String description;
    SQL実行結果のマッピ
    ングと捉え、ロジックを
    書かないようにした

    View Slide

  46. ● Javaといいつつほとんどアノテーション
    ● domaのコードはDDDでいうところのドメイン層ではなくインフラ
    層と考える(domaのEntityはDDDでいうドメイン層のEntityでは
    なく、インフラ層のSQL実行結果を抽象化したデータと捉え、
    Kotlinで書いたドメイン層において、ドメインオブジェクトに都度
    マッピングする)

    View Slide

  47. Kotlinの部分: Repository
    @Repository
    class FilmDomaRepository(
    ...
    ) {
    fun findAll(): List {
    val entities = filmDao.selectAll()
    return Film.createByFilmEntities(entities)
    }
    Java(DomaのEntity)からKotlin(Model,
    ValueObject)に
    マッピング

    View Slide

  48. Kotlinの部分: Model, ValueObject
    class Film(
    val id: Int? = null,
    val title: String,
    val description: String?,
    val language: Language,
    val actors: List,
    val categories: List
    ) {
    ...

    View Slide

  49. Kotlinの部分: Service
    @Service
    class FilmApplicationService(
    ...
    ) {
    @Transactional
    fun create(command: FilmCommand): Int {
    val film = Film(
    command = command,
    language = langRepo.findById(command.languageId) ?: throw NotFoundException(),
    actors = actorRepo.findByIds(command.actorIds),
    categories = categoryRepo.findByIds(command.categoryIds)
    )
    val filmId = filmRepo.store(film)
    mailService.sendCreated(film)
    return filmId
    }

    View Slide

  50. Kotlinの部分: Controller
    @RestController
    @RequestMapping("/api/v1/films")
    class FilmRestController(
    private val filmRepository: FilmDomaRepository
    ) {
    @GetMapping
    fun index(): List
    = filmRepository.findAll().map(::FilmResource)

    View Slide

  51. よく触る部分は
    全部Kotlin!

    View Slide

  52. まとめ
    ● Kotlin x Spring BootでAPIサーバーは特に問題なく作れる
    ● kotlin-springプラグインのおかげでopen不要
    ● jackson-module-kotlinのおかげでdata classとのマッピングが楽に
    ● doma2はKotlinを実験的にサポート
    ○domaのentityをKotlinにできる。だが、kaptの不安定な挙動リスクあり
    ● 私達はdoma2の層をjavaにした
    ○ドメイン層、アプリケーション層はKotlinなのでほぼKotlin
    ● Kotlinかわいい(^ω^)ペロペロ

    View Slide

  53. エムスリー love Kotlin
    ● 別件のリニューアルにもサーバーサイドKotlinを採用予定
    ● 新規Androidアプリ開発はKotlin
    ● 新規Webアプリ開発の際にも積極的にKotlinを検討

    View Slide

  54. WE'RE HIRING!
    Kotlinerの方々、
    一緒にやりましょう!

    View Slide