Slide 1

Slide 1 text

Spring and Kotlin ~CA.kt #4 KotlinConf報告会~ 2017-11-14 長澤太郎 @ngsw_taro

Slide 2

Slide 2 text

自己紹介 ● 長澤 太郎(たろーって呼んでね) ● @ngsw_taro ● エムスリー株式会社 ● ディズニーが大好き! エムスリーは 国内最大規模の 医療情報プラット フォームを開発・ 運営しています。 エンジニア募 集中!

Slide 3

Slide 3 text

Kotlin Webアプリケーション 10/6発売! Kotlinイン・アクション 10/31発売!

Slide 4

Slide 4 text

KotlinConf 行ってきました!

Slide 5

Slide 5 text

サーバサイドKotlin キテます! 44セッション中... ● サーバサイドKotlin: 5個(内、Spring関連 3個) ● Android: 4個 ● Native(iOS含む): 3個 ● JS: 2個 ● その他プラットフォーム: 3個 ● その他(言語機能、ベストプラクティスなど): 28個

Slide 6

Slide 6 text

サーバサイドKotlin キテます! 44セッション中... ● サーバサイドKotlin: 5個(内、Spring関連 3個) ● Android: 4個 ● Native(iOS含む): 3個 ● JS: 2個 ● その他プラットフォーム: 3個 ● その他(言語機能、ベストプラクティスなど): 28個

Slide 7

Slide 7 text

Bootiful Kotlin

Slide 8

Slide 8 text

Spring Framework 5.0 リリース! ● 2017/9/28にSpring Framework 5.0がリリース ● Spring WebFluxの追加 ○ ノンブロッキングでリアクティブなWebフレームワーク ○ 従来のアノテーションによるハンドラの宣言に加え ○ ラムダを使用したルーティング ● ラムダを使用したBean登録 ● KotlinフレンドリなAPI群の追加

Slide 9

Slide 9 text

基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array) { runApplication(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }

Slide 10

Slide 10 text

基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array) { runApplication(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }

Slide 11

Slide 11 text

基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array) { runApplication(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }

Slide 12

Slide 12 text

基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array) { runApplication(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }

Slide 13

Slide 13 text

基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array) { runApplication(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }

Slide 14

Slide 14 text

基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array) { runApplication(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }

Slide 15

Slide 15 text

ラムダを使用したbean登録 (1) @SpringBootApplication class DemoApplication fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean { Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } }) .run(*args) }

Slide 16

Slide 16 text

ラムダを使用したbean登録 (1) @SpringBootApplication class DemoApplication fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean { Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } }) .run(*args) }

Slide 17

Slide 17 text

ラムダを使用したbean登録 (1) @SpringBootApplication class DemoApplication fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean { Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } }) .run(*args) }

Slide 18

Slide 18 text

ラムダを使用したbean登録 (1) @SpringBootApplication class DemoApplication fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean { Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } }) .run(*args) }

Slide 19

Slide 19 text

ラムダを使用したbean登録 (2) beans { bean { Foo() } bean { Bar(ref()) } } @Bean fun foo(): Foo = Foo() @Bean fun bar(foo: Foo): Bar = Bar(foo)

Slide 20

Slide 20 text

ラムダを使用したbean登録 (2) beans { bean { Foo() } bean { Bar(ref()) } } @Bean fun foo(): Foo = Foo() @Bean fun bar(foo: Foo): Bar = Bar(foo) 推論できれば省略可

Slide 21

Slide 21 text

WebFlux @RestController @RequestMapping("users") class UserController { @GetMapping fun index(): Mono> = Mono.just(listOf(User("Taro", "Nagasawa"))) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index():List = listOf(User("Taro", "Nagasawa")) }

Slide 22

Slide 22 text

WebFlux @RestController @RequestMapping("users") class UserController { @GetMapping fun index(): Mono> = Mono.just(listOf(User("Taro", "Nagasawa"))) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index():List = listOf(User("Taro", "Nagasawa")) } MonoやFluxを返すだけ

Slide 23

Slide 23 text

ラムダによるルーティング data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean { /* Jacksonの設定 */ } bean { router { GET("/users") { val users = listOf(User("Taro", "Nagasawa")) ServerResponse.ok().body(Mono.just(users)) } } } }) .run(*args) }

Slide 24

Slide 24 text

ラムダによるルーティング data class User(val firstName: String, val lastName: String) @SpringBootApplication class DemoApplication fun main(args: Array) { SpringApplicationBuilder() .sources(DemoApplication::class.java) .initializers(beans { bean { /* Jacksonの設定 */ } bean { router { GET("/users") { val users = listOf(User("Taro", "Nagasawa")) ServerResponse.ok().body(Mono.just(users)) } } } }) .run(*args) }

Slide 25

Slide 25 text

おまけ: Exposed ● Kotlin SQLライブラリ ○ SQL DSL ○ Data Access Object ● JetBrainsが開発 https://github.com/JetBrains/Exposed ● ver. 0.8.9 ● 有名なDBMSに対応: ポスグレ, MySQL, Oracle, ...

Slide 26

Slide 26 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } }

Slide 27

Slide 27 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } } テーブル定義

Slide 28

Slide 28 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } } DBに接続

Slide 29

Slide 29 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } } トランザクション

Slide 30

Slide 30 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } } テーブル作成 テーブル削除

Slide 31

Slide 31 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } } インサート

Slide 32

Slide 32 text

SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Teams.insert { it[name] = "Kotlin" } Teams.selectAll().forEach { row -> println("id=${row[Teams.id]}: ${row[Teams.name]}") // id=1: Kotlin } SchemaUtils.drop(Teams) } } 取得

Slide 33

Slide 33 text

一対多 関連 object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } object Tasks: Table() { val id = long("id").autoIncrement().primaryKey() val content = text("content") val completedAt = datetime("completed_at").nullable() val teamId = long("team_id") references Teams.id }

Slide 34

Slide 34 text

一対多 関連 object Teams: Table() { val id = long("id").autoIncrement().primaryKey() val name = varchar("name", length = 100) } object Tasks: Table() { val id = long("id").autoIncrement().primaryKey() val content = text("content") val completedAt = datetime("completed_at").nullable() val teamId = long("team_id") references Teams.id } 外部キー設定

Slide 35

Slide 35 text

joinとprojectionとwhere Tasks.innerJoin(Teams) .slice(Team.name, Tasks.content) .select { Tasks.completedAt.isNull() } .forEach { row -> println("${row[Teams.name]}: ${row[Tasks.content]}") } id name 1 Kotlin id content completed_at team_id 1 release 1.1.60 2017-11-14T03:00+09:00 1 2 release 1.2.0 (null) 1 Teams Tasks Teams.name Tasks.content Kotlin release 1.2.0

Slide 36

Slide 36 text

Data Access Object object Teams: LongIdTable() { val name = varchar("name", length = 100) } class Team(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Teams) var name by Teams.name } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Team.new { name = "Kotlin" } Team.all().forEach { team: Team -> println("id=${team.id}, name=${team.name}") } } }

Slide 37

Slide 37 text

Data Access Object object Teams: LongIdTable() { val name = varchar("name", length = 100) } class Team(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Teams) var name by Teams.name } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Team.new { name = "Kotlin" } Team.all().forEach { team: Team -> println("id=${team.id}, name=${team.name}") } } }

Slide 38

Slide 38 text

Data Access Object object Teams: LongIdTable() { val name = varchar("name", length = 100) } class Team(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Teams) var name by Teams.name } fun main(args: Array) { Database.connect("jdbc:h2:mem:test", "org.h2.Driver") transaction { SchemaUtils.create(Teams) Team.new { name = "Kotlin" } Team.all().forEach { team: Team -> println("id=${team.id}, name=${team.name}") } } }

Slide 39

Slide 39 text

一対多 関連 object Teams: LongIdTable() { val name = varchar("name", length = 100) } object Tasks: LongIdTable() { val content: text("content") val completedAt: datetime("completed_at").nullable() val team = reference("team", Teams) } class Team(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Teams) var name by Teams.name val tasks by Tasks.referrersOn(Tasks.team) } class Task(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Tasks) var content by Tasks.content var completedAt by Tasks.completedAt var team by Team.referencedOn(Tasks.team) }

Slide 40

Slide 40 text

一対多 関連 object Teams: LongIdTable() { val name = varchar("name", length = 100) } object Tasks: LongIdTable() { val content: text("content") val completedAt: datetime("completed_at").nullable() val team = reference("team", Teams) } class Team(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Teams) var name by Teams.name val tasks by Tasks.referrersOn(Tasks.team) } class Task(id: EntityID): LongEntity(id) { companion object: LongEntityClass(Tasks) var content by Tasks.content var completedAt by Tasks.completedAt var team by Team.referencedOn(Tasks.team) } 関連を定義

Slide 41

Slide 41 text

取得 Task .find { Tasks.completedAt.isNull() } .forEach { task: Task -> val team: Team = task.team println("${team.name}: ${task.content}") }

Slide 42

Slide 42 text

取得 Task .find { Tasks.completedAt.isNull() } .forEach { task: Task -> val team: Team = task.team println("${team.name}: ${task.content}") } N + 1 のおそれ

Slide 43

Slide 43 text

まとめ ● JVMでさくっとWebアプリが作れる! ● Spring ♡ Kotlin ● アノテーションによる宣言→ラムダによるDSL ● Exposedで簡単DB操作

Slide 44

Slide 44 text

まとめ ● JVMでさくっとWebアプリが作れる! ● Spring ♡ Kotlin ● アノテーションによる宣言→ラムダによるDSL ● Exposedで簡単DB操作 今こそKotlinで Webアプリを!