Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

長澤 太郎

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Spring Initializr - start.spring.io

Slide 7

Slide 7 text

Spring Initializr - start.spring.io

Slide 8

Slide 8 text

IntelliJ IDEA

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Kotlin事情: デフォルトで継承を許可しない ● Kotlinのクラスはデフォルトで継承を許可しない ● open修飾子を付けることで継承を許可する ● @Serviceなどが付いたクラスはSpringによってサブクラス が生成される(継承を許可する必要がある) @Service open class FooService { ... } @Service open class BarService { ... } 面倒だしダサい

Slide 16

Slide 16 text

大丈夫!基本的に意識する必要なし! ● Kotlin公式 allopenプラグイン ○ 自分で指定したアノテーションが付与されたクラスをすべてopenクラス として扱ってくれるプラグイン ● kotlin-springプラグイン ○ Spring用allopenプラグイン ○ 予め@Serviceや@Configurationのようなアノテーションが allopen対象として登録されている Spring Initializrで生成したプロジェクトには 最初から設定されているので意識する必要はない

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Kotlin事情: バリデーションに注意 class PostBody( @NotNull val value: Int ) class PostBody( @field:NotNull val value: Int? )

Slide 21

Slide 21 text

Kotlinでも○○を使って bean登録やハンドラ定義 答. アノテーション

Slide 22

Slide 22 text

おまけ もしフィールドインジェクションがしたいなら... @RestController class HelloWorldController { @Autowired lateinit var helloWorldService: HelloWorldService @GetMapping("/hello-world") fun helloWorld(): String { ... } }

Slide 23

Slide 23 text

○○を使った比較的新しい bean登録やハンドラ定義

Slide 24

Slide 24 text

Bean Definition DSL fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean() bean() }) .run(*args) }

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

[NEW] Router DSL val userHandler = ref() 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) } }

Slide 27

Slide 27 text

○○を使った比較的新しい bean登録やハンドラ定義 答. Kotlin用 DSL

Slide 28

Slide 28 text

おまけ ● ktlint - いわゆる Linter 兼 Formatter ● Gradleプラグインがある ● IntelliJ IDEAのフォーマッタの自動設定あり $ ./gradlew ktlintCheck $ ./gradlew ktlintFormat

Slide 29

Slide 29 text

WebFluxでKotlinの ○○という機能が便利

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Kotlinにはコルーチンがある! ● Reactor対応のライブラリが公式である ○ コルーチンでMonoやFluxを表現できる ● というかむしろSpring Framework 5.2から○○! ● 詳しくは次の木原さんの発表で!

Slide 36

Slide 36 text

WebFluxでKotlinの ○○という機能が便利 答. コルーチン

Slide 37

Slide 37 text

おまけ ● Ktor というKotlin用Webアプリフレームワークがいい 感じ ● JetBrains公式 ● 非常に薄く、余分な機能は3rdパーティ任せ ○ ロギング、永続化、テンプレートエンジン、DI ● DSL ○ ラムダ(特に拡張関数としてのラムダ)を多様 ○ 宣言的にプログラムを組み立てる ● ノンブロッキング ○ 複雑な非同期プログラミングをコルーチンで

Slide 38

Slide 38 text

Kotlinでも○○を使ったテスト

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

client.get() .uri("/hello-world") .exchange() .expectBody(HelloWorldResource::class.java) .isEqualTo(expectedResource) WebTestClientによるテスト Javaと同じ感覚で 書いてるとこうなりそう

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

おまけ ● アサーションライブラリは何がいいか ● 弊社では AssertJを使っています ● assertkが気になる ○ Kotlinフレンドリ(nullまわりとか) ○ ただし v0.21 で不安定か? assertThat(yourName).isEqualTo("Alice")

Slide 47

Slide 47 text

Kotlinでモックするなら ○○がイイ感じ

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

MockK interface UserRepository { suspend fun findUser(id: Long): User? } val userRepo = mockk() every { userRepo.findUser(1) } returns user coEvery { userRepo.findUser(1) } returns user 今回はコルーチン(suspend関数)なので

Slide 50

Slide 50 text

モック生成を繰り返さないこと 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

Slide 51

Slide 51 text

モック生成は一度、都度リセット 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

Slide 52

Slide 52 text

そのほか基本的なことは一通りできる // キャプチャ val slot = slot() // 戻り値がないメソッド every { myService.run(capture(slot)) } just runs // 検証 verify(exactly = 1) { myService.run(any()) }

Slide 53

Slide 53 text

Kotlinでモックするなら ○○がイイ感じ 答. Mockk

Slide 54

Slide 54 text

まとめ ● Spring InitializrやIntelliJで簡単にSpring Kotlinを始めら れる! ● 普通にアノテーションを使ってもいいし、DSLを使ってもbean 登録やルーティングができる ○ Kotlin固有の問題はあまりないが、バリデーションに注意 ● コルーチンというものが便利っぽいぞ ● KotlinでもJUnitでSpringテストができる ● モックライブラリはMockKがよさそう

Slide 55

Slide 55 text

「Spring Kotlin, 完全に理解した!」

Slide 56

Slide 56 text

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