Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

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

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

Taro Nagasawa

January 27, 2020
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

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

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

    "Hello, world!" } @RestController class HelloWorldController(val helloWorldService: HelloWorldService) { @GetMapping("/hello-world") fun helloWorld(): String = helloWorldService.helloWorld() }
  3. 大丈夫!基本的に意識する必要なし! • Kotlin公式 allopenプラグイン ◦ 自分で指定したアノテーションが付与されたクラスをすべてopenクラス として扱ってくれるプラグイン • kotlin-springプラグイン ◦

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

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

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

    fun create( @Valid @RequestBody body: PostBody, bindingResult: BindingResult ) { ... } プロパティはJavaで言う フィールドとアクセサが組み合 わさったようなもの NotNull型 + Javaのプリミティ ブ型 = ...!?
  7. [NEW] Router DSL bean { val userService = ref<UserService>() router

    { "/api".nest { GET("/users") { val users = userService.findAll() ok().body(users) } } } }
  8. [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) } }
  9. おまけ • ktlint - いわゆる Linter 兼 Formatter • Gradleプラグインがある

    • IntelliJ IDEAのフォーマッタの自動設定あり $ ./gradlew ktlintCheck $ ./gradlew ktlintFormat
  10. 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 } }
  11. 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を同時に取得して ペアとしてまとめる
  12. 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の入れ子を解除
  13. 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の結果のプロパティで変換
  14. 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の結果のプロパティで変換
  15. おまけ • Ktor というKotlin用Webアプリフレームワークがいい 感じ • JetBrains公式 • 非常に薄く、余分な機能は3rdパーティ任せ ◦

    ロギング、永続化、テンプレートエンジン、DI • DSL ◦ ラムダ(特に拡張関数としてのラムダ)を多様 ◦ 宣言的にプログラムを組み立てる • ノンブロッキング ◦ 複雑な非同期プログラミングをコルーチンで
  16. class FooTest { @Nested inner class fooMethod { @Test fun

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

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

    `should throw exception`() { assertThrows<MyException>() { Foo().foo() } } } } JUnit5
  19. MockK val userRepo = mockk<UserRepository>() every { userRepo.findUser(1) } returns

    user 通常のメソッドであれば このように挙動を変更できる interface UserRepository { suspend fun findUser(id: Long): User? }
  20. 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関数)なので
  21. モック生成を繰り返さないこと 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
  22. モック生成は一度、都度リセット 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
  23. そのほか基本的なことは一通りできる // キャプチャ val slot = slot<String>() // 戻り値がないメソッド every

    { myService.run(capture(slot)) } just runs // 検証 verify(exactly = 1) { myService.run(any()) }
  24. まとめ • Spring InitializrやIntelliJで簡単にSpring Kotlinを始めら れる! • 普通にアノテーションを使ってもいいし、DSLを使ってもbean 登録やルーティングができる ◦

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