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

0ff63a1cf64b2501566f37581000bd9b?s=47 Hidenori Maehara
September 28, 2017

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

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

0ff63a1cf64b2501566f37581000bd9b?s=128

Hidenori Maehara

September 28, 2017
Tweet

Transcript

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

    @maeharin
  2. サンプルコード、githubにあげてます https://github.com/maeharin/kotlin-dvd-rental • Kotlin x Spring Boot x Doma2で作ったAPIサーバーのサンプル •

    DBにPostgreSQL、検索にElasticsearch(Dockerで起動) • Postgresql tutorialのDVD Rentalサンプルデータベースを利用
  3. http://www.postgresqltutorial.com/postgresql-sample-database/

  4. 自己紹介 • 前原 秀徳 • @maeharin(まえはりん) • エムスリー株式会社 エンジニア •

    チームリーダー、グループ会社取締役等を歴任 • 自慢:ブログ記事が、はてぶ1200 ◦http://maeharin.hatenablog.com/
  5. エムスリーって何の会社? ・医療に関するWebサービスを多数展開 ・全世界で約400万人の医師会員 ・日本で約25万人の医師会員

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

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

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

    • Kotlin x Jackson • Kotlin x Doma2
  9. Kotlin x Spring Boot でHello API

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

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

  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で書く
  13. 3) ./gradlew bootRun $ ./gradlew bootRun

  14. 以上!

  15. Kotlin x Springの各種機能

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

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

  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(抜粋)
  19. controller @RestController @RequestMapping("/api/v1/films") class FilmRestController( private val filmRepository: FilmDomaRepository )

    { @GetMapping fun index(): List<FilmResource> = ... @PostMapping fun create( @RequestBody @Validated filmParam: FilmParam ): Int { open不要 springの アノテーションは普 通に使える
  20. @Service class FilmApplicationService( private val filmRepository: FilmDomaRepository ) { @Transactional

    fun create(command: FilmCommand): Int { @Repository class FilmDomaRepository( private val filmDao: FilmDao ) { DI
  21. @Configuration class ObjectMapperConfig { @Bean fun objectMapper(): ObjectMapper = ObjectMapper()

    .registerModule(JavaTimeModule()) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .registerModule(KotlinModule()) } Config
  22. SpringSecurity @Configuration @EnableAuthorizationServer class Oauth2AuthorizationServerConfig( private val authManager: AuthenticationManager, private

    val dataSource: DataSource ) : AuthorizationServerConfigurerAdapter() { @Bean fun passwordEncoder(): BCryptPasswordEncoder = BCryptPasswordEncoder() Spring周辺の ライブラリも 問題なく使える
  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() }
  24. None
  25. Kotlin x Jackson

  26. data class FilmResource( var name: String = "", var isAdmin:

    Boolean = false, var companyId: Int = 0 ) APIへのリクエスト(Json)をKotlinへマッピング Jacksonは デフォルト値ありの コンストラクタを要 求 デフォルト値は使い たくないのに。。
  27. こうしたい data class FilmResource( val name: String, val isAdmin: Boolean,

    val companyId: Int )
  28. jackson-module-kotlin

  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

  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
  31. searchResponse.hits.map { hit -> val json = hit.sourceAsString val filmSource:

    FilmSource = objectMapper.readValue(json) } ElasticSearchとJsonやり取りする時も嬉しい json => data class
  32. Kotlin x Doma2

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

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

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

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

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

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

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

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

    ↓ • 回避策はあるかもしれないが、できればハマるリスクを少なく したい
  41. Domaの層は全てJavaで書く。という選択肢 http://doma.readthedocs.io/ja/stable/kotlin-support/

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

    • doma-genを使わないとしても、Domaのコードにロジックを書かなければ Javaのツラミはあまり感じない
  43. ディレクトリ例 Java domaに関するコード のみ Kotlin 上記以外全て SQL

  44. Javaの部分: DomaのDao @ConfigAutowireable @Dao public interface FilmEntityDao { @Select FilmEntity

    selectById(Integer filmId); @Select List<FilmEntity> selectAll(); @Insert int insert(FilmEntity entity);
  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実行結果のマッピ ングと捉え、ロジックを 書かないようにした
  46. • Javaといいつつほとんどアノテーション • domaのコードはDDDでいうところのドメイン層ではなくインフラ 層と考える(domaのEntityはDDDでいうドメイン層のEntityでは なく、インフラ層のSQL実行結果を抽象化したデータと捉え、 Kotlinで書いたドメイン層において、ドメインオブジェクトに都度 マッピングする)

  47. Kotlinの部分: Repository @Repository class FilmDomaRepository( ... ) { fun findAll():

    List<Film> { val entities = filmDao.selectAll() return Film.createByFilmEntities(entities) } Java(DomaのEntity)からKotlin(Model, ValueObject)に マッピング
  48. Kotlinの部分: Model, ValueObject class Film( val id: Int? = null,

    val title: String, val description: String?, val language: Language, val actors: List<Actor>, val categories: List<Category> ) { ...
  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 }
  50. Kotlinの部分: Controller @RestController @RequestMapping("/api/v1/films") class FilmRestController( private val filmRepository: FilmDomaRepository

    ) { @GetMapping fun index(): List<FilmResource> = filmRepository.findAll().map(::FilmResource)
  51. よく触る部分は 全部Kotlin!

  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かわいい(^ω^)ペロペロ
  53. エムスリー love Kotlin • 別件のリニューアルにもサーバーサイドKotlinを採用予定 • 新規Androidアプリ開発はKotlin • 新規Webアプリ開発の際にも積極的にKotlinを検討

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