KotlinでSpring 完全理解ガイド #jsug

KotlinでSpring 完全理解ガイド #jsug

日本Springユーザグループの勉強会、登壇資料
https://jsug.doorkeeper.jp/events/102390

Java x Springユーザ向け資料です

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

January 27, 2020
Tweet

Transcript

  1. 2020-01-27 長澤 太郎 KotlinでSpring 完全理解ガイド

  2. 本発表のゴール チョットデキル 何もわからない 完全に理解した ここまで ガイドします!

  3. より具体的には 「Kotlinでも普通にSpring使えるんだ!」 「Kotlinにはこういう事情があるんだ!」 が わかるようになる!

  4. 長澤 太郎

  5. ◦◦を使えば Kotlinで簡単に Spring開発を始められる

  6. Spring Initializr - start.spring.io

  7. Spring Initializr - start.spring.io

  8. IntelliJ IDEA

  9. すぐに開発を始められる!

  10. ◦◦を使えば Kotlinで簡単に Spring開発を始められる 答. Spring Initializr

  11. おまけ ビルドツールにGradleを選択すると ビルド設定ファイルがbuild.gradle.kts として生成され、そのスクリプトが Kotlinで記述されている

  12. Kotlinでも◦◦を使って bean登録やハンドラ定義

  13. Hello World @Service class HelloWorldService { fun helloWorld(): String =

    "Hello, world!" } @RestController class HelloWorldController(val helloWorldService: HelloWorldService) { @GetMapping("/hello-world") fun helloWorld(): String = helloWorldService.helloWorld() }
  14. Hello World @Service class HelloWorldService { fun helloWorld(): String =

    "Hello, world!" } @RestController class HelloWorldController(val helloWorldService: HelloWorldService) { @GetMapping("/hello-world") fun helloWorld(): String = helloWorldService.helloWorld() }
  15. Kotlin事情: デフォルトで継承を許可しない • Kotlinのクラスはデフォルトで継承を許可しない • open修飾子を付けることで継承を許可する • @Serviceなどが付いたクラスはSpringによってサブクラス が生成される(継承を許可する必要がある) @Service

    open class FooService { ... } @Service open class BarService { ... } 面倒だしダサい
  16. 大丈夫!基本的に意識する必要なし! • Kotlin公式 allopenプラグイン ◦ 自分で指定したアノテーションが付与されたクラスをすべてopenクラス として扱ってくれるプラグイン • kotlin-springプラグイン ◦

    Spring用allopenプラグイン ◦ 予め@Serviceや@Configurationのようなアノテーションが allopen対象として登録されている Spring Initializrで生成したプロジェクトには 最初から設定されているので意識する必要はない
  17. Kotlin事情: バリデーションに注意 class PostBody( @NotNull val value: Int ) @PostMapping

    fun create( @Valid @RequestBody body: PostBody, bindingResult: BindingResult ) { ... }
  18. Kotlin事情: バリデーションに注意 class PostBody( @NotNull val value: Int ) @PostMapping

    fun create( @Valid @RequestBody body: PostBody, bindingResult: BindingResult ) { ... } こっちは問題なし
  19. Kotlin事情: バリデーションに注意 class PostBody( @NotNull val value: Int ) @PostMapping

    fun create( @Valid @RequestBody body: PostBody, bindingResult: BindingResult ) { ... } プロパティはJavaで言う フィールドとアクセサが組み合 わさったようなもの NotNull型 + Javaのプリミティ ブ型 = ...!?
  20. Kotlin事情: バリデーションに注意 class PostBody( @NotNull val value: Int ) class

    PostBody( @field:NotNull val value: Int? )
  21. Kotlinでも◦◦を使って bean登録やハンドラ定義 答. アノテーション

  22. おまけ もしフィールドインジェクションがしたいなら... @RestController class HelloWorldController { @Autowired lateinit var helloWorldService:

    HelloWorldService @GetMapping("/hello-world") fun helloWorld(): String { ... } }
  23. ◦◦を使った比較的新しい bean登録やハンドラ定義

  24. Bean Definition DSL fun main(args: Array<String>) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans

    { bean<UserRepositoryImpl>() bean<UserService>() }) .run(*args) }
  25. [NEW] Router DSL bean { val userService = ref<UserService>() router

    { "/api".nest { GET("/users") { val users = userService.findAll() ok().body(users) } } } }
  26. [NEW] Router DSL val userHandler = ref<UserHandler>() router { "/api".nest

    { GET("/users", userHandler::findAll) } } class UserHandler(val userService: UserService) { fun findAll(req: ServerRequest): ServerResponse { val users = userService.findAll() return ServerResponse.ok().body(users) } }
  27. ◦◦を使った比較的新しい bean登録やハンドラ定義 答. Kotlin用 DSL

  28. おまけ • ktlint - いわゆる Linter 兼 Formatter • Gradleプラグインがある

    • IntelliJ IDEAのフォーマッタの自動設定あり $ ./gradlew ktlintCheck $ ./gradlew ktlintFormat
  29. WebFluxでKotlinの ◦◦という機能が便利

  30. Kotlinでも普通にWebFlux @RestController class DemoController(val demoService: DemoService) { @GetMapping("demo") fun handle():

    Mono<String> = Mono.zip( demoService.getMonoA(), demoService.getMonoB() ) .flatMap { demoService.getMonoC(it.t1, it.t2) } .map { it.answer } }
  31. Kotlinでも普通にWebFlux @RestController class DemoController(val demoService: DemoService) { @GetMapping("demo") fun handle():

    Mono<String> = Mono.zip( demoService.getMonoA(), demoService.getMonoB() ) .flatMap { demoService.getMonoC(it.t1, it.t2) } .map { it.answer } } AとBを同時に取得して ペアとしてまとめる
  32. Kotlinでも普通にWebFlux @RestController class DemoController(val demoService: DemoService) { @GetMapping("demo") fun handle():

    Mono<String> = Mono.zip( demoService.getMonoA(), demoService.getMonoB() ) .flatMap { demoService.getMonoC(it.t1, it.t2) } .map { it.answer } } AとBを同時に取得して ペアとしてまとめる CをMonoとして取得 Monoの入れ子を解除
  33. Kotlinでも普通にWebFlux @RestController class DemoController(val demoService: DemoService) { @GetMapping("demo") fun handle():

    Mono<String> = Mono.zip( demoService.getMonoA(), demoService.getMonoB() ) .flatMap { demoService.getMonoC(it.t1, it.t2) } .map { it.answer } } AとBを同時に取得して ペアとしてまとめる CをMonoとして取得 Monoの入れ子を解除 Cの結果のプロパティで変換
  34. Kotlinでも普通にWebFlux @RestController class DemoController(val demoService: DemoService) { @GetMapping("demo") fun handle():

    Mono<String> = Mono.zip( demoService.getMonoA(), demoService.getMonoB() ) .flatMap { demoService.getMonoC(it.t1, it.t2) } .map { it.answer } } AとBを同時に取得して ペアとしてまとめる CをMonoとして取得 Monoの入れ子を解除 Cの結果のプロパティで変換
  35. Kotlinにはコルーチンがある! • Reactor対応のライブラリが公式である ◦ コルーチンでMonoやFluxを表現できる • というかむしろSpring Framework 5.2から◦◦! •

    詳しくは次の木原さんの発表で!
  36. WebFluxでKotlinの ◦◦という機能が便利 答. コルーチン

  37. おまけ • Ktor というKotlin用Webアプリフレームワークがいい 感じ • JetBrains公式 • 非常に薄く、余分な機能は3rdパーティ任せ ◦

    ロギング、永続化、テンプレートエンジン、DI • DSL ◦ ラムダ(特に拡張関数としてのラムダ)を多様 ◦ 宣言的にプログラムを組み立てる • ノンブロッキング ◦ 複雑な非同期プログラミングをコルーチンで
  38. Kotlinでも◦◦を使ったテスト

  39. class FooTest { @Nested inner class fooMethod { @Test fun

    `should throw exception`() { assertThrows<MyException>() { Foo().foo() } } } } JUnit5
  40. class FooTest { @Nested inner class fooMethod { @Test fun

    `should throw exception`() { assertThrows<MyException>() { Foo().foo() } } } } JUnit5 グルーピングしてテストの見通しを良く
  41. class FooTest { @Nested inner class fooMethod { @Test fun

    `should throw exception`() { assertThrows<MyException>() { Foo().foo() } } } } JUnit5
  42. client.get() .uri("/hello-world") .exchange() .expectBody(HelloWorldResource::class.java) .isEqualTo<Nothing>(expectedResource) WebTestClientによるテスト Javaと同じ感覚で 書いてるとこうなりそう

  43. client.get() .uri("/hello-world") .exchange() .expectBody(HelloWorldResource::class.java) .isEqualTo<Nothing>(expectedResource) WebTestClientによるテスト Javaと同じ感覚で 書いてるとこうなりそう ここで例外が発生する!!

  44. client.get() .uri("/hello-world") .exchange() .expectBody<HelloWorldResource>() .isEqualTo(expectedResource) Kotlin用の拡張関数 expectBodyを使う

  45. Kotlinでも◦◦を使ったテスト 答. JUnit

  46. おまけ • アサーションライブラリは何がいいか • 弊社では AssertJを使っています • assertkが気になる ◦ Kotlinフレンドリ(nullまわりとか)

    ◦ ただし v0.21 で不安定か? assertThat(yourName).isEqualTo("Alice")
  47. Kotlinでモックするなら ◦◦がイイ感じ

  48. MockK val userRepo = mockk<UserRepository>() every { userRepo.findUser(1) } returns

    user 通常のメソッドであれば このように挙動を変更できる interface UserRepository { suspend fun findUser(id: Long): User? }
  49. MockK interface UserRepository { suspend fun findUser(id: Long): User? }

    val userRepo = mockk<UserRepository>() every { userRepo.findUser(1) } returns user coEvery { userRepo.findUser(1) } returns user 今回はコルーチン(suspend関数)なので
  50. モック生成を繰り返さないこと class DesignControllerTest { private lateinit var repo: DesignRepository private

    lateinit var client: DesignClient private lateinit var controller: DesignController @BeforeEach fun init() { repo = mockk() client = mockk() controller = DesignController(repo, client) } } 高コスト 参考 https://www.youtube.com/watch?v=RX_g65J14H0
  51. モック生成は一度、都度リセット class DesignControllerTest { private val repo: DesignRepository = mockk()

    private val client: DesignClient = mockk() private val controller: DesignController(repo, client) @BeforeEach fun init() { clearMocks(repo, client) } } 参考 https://www.youtube.com/watch?v=RX_g65J14H0
  52. そのほか基本的なことは一通りできる // キャプチャ val slot = slot<String>() // 戻り値がないメソッド every

    { myService.run(capture(slot)) } just runs // 検証 verify(exactly = 1) { myService.run(any()) }
  53. Kotlinでモックするなら ◦◦がイイ感じ 答. Mockk

  54. まとめ • Spring InitializrやIntelliJで簡単にSpring Kotlinを始めら れる! • 普通にアノテーションを使ってもいいし、DSLを使ってもbean 登録やルーティングができる ◦

    Kotlin固有の問題はあまりないが、バリデーションに注意 • コルーチンというものが便利っぽいぞ • KotlinでもJUnitでSpringテストができる • モックライブラリはMockKがよさそう
  55. 「Spring Kotlin, 完全に理解した!」

  56. よくある質問 「DBアクセスは どうしてる?」