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

Spring and Kotlin #ca_kt

Spring and Kotlin #ca_kt

CA.kt #4 (Kotlin Conf報告会) ( https://cyberagent.connpass.com/event/70423/ )で発表した資料です。

14c9795d267f5b85abb98ca5e8780646?s=128

Taro Nagasawa

November 15, 2017
Tweet

Transcript

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

  2. 自己紹介 • 長澤 太郎(たろーって呼んでね) • @ngsw_taro • エムスリー株式会社 • ディズニーが大好き!

    エムスリーは 国内最大規模の 医療情報プラット フォームを開発・ 運営しています。 エンジニア募 集中!
  3. Kotlin Webアプリケーション 10/6発売! Kotlinイン・アクション 10/31発売!

  4. KotlinConf 行ってきました!

  5. サーバサイドKotlin キテます! 44セッション中... • サーバサイドKotlin: 5個(内、Spring関連 3個) • Android: 4個

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

    • Native(iOS含む): 3個 • JS: 2個 • その他プラットフォーム: 3個 • その他(言語機能、ベストプラクティスなど): 28個
  7. Bootiful Kotlin

  8. Spring Framework 5.0 リリース! • 2017/9/28にSpring Framework 5.0がリリース • Spring

    WebFluxの追加 ◦ ノンブロッキングでリアクティブなWebフレームワーク ◦ 従来のアノテーションによるハンドラの宣言に加え ◦ ラムダを使用したルーティング • ラムダを使用したBean登録 • KotlinフレンドリなAPI群の追加
  9. 基本 data class User(val firstName: String, val lastName: String) @SpringBootApplication

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

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

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

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

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

    class DemoApplication { @Bean fun objectMapperBuilder() = Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index() = listOf(User("Taro", "Nagasawa")) }
  15. ラムダを使用したbean登録 (1) @SpringBootApplication class DemoApplication fun main(args: Array<String>) { SpringApplicationBuilder()

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

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

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

    .sources(DemoApplication::class.java) .initializers(beans { bean { Jackson2ObjectMapperBuilder() .propertyNamingStrategy(SNAKE_CASE) } }) .run(*args) }
  19. ラムダを使用したbean登録 (2) beans { bean { Foo() } bean {

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

    Bar(ref<Foo>()) } } @Bean fun foo(): Foo = Foo() @Bean fun bar(foo: Foo): Bar = Bar(foo) 推論できれば省略可
  21. WebFlux @RestController @RequestMapping("users") class UserController { @GetMapping fun index(): Mono<List<User>>

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

    = Mono.just(listOf(User("Taro", "Nagasawa"))) } @RestController @RequestMapping("users") class UserController { @GetMapping fun index():List<User> = listOf(User("Taro", "Nagasawa")) } MonoやFluxを返すだけ
  23. ラムダによるルーティング data class User(val firstName: String, val lastName: String) @SpringBootApplication

    class DemoApplication fun main(args: Array<String>) { 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) }
  24. ラムダによるルーティング data class User(val firstName: String, val lastName: String) @SpringBootApplication

    class DemoApplication fun main(args: Array<String>) { 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) }
  25. おまけ: Exposed • Kotlin SQLライブラリ ◦ SQL DSL ◦ Data

    Access Object • JetBrainsが開発 https://github.com/JetBrains/Exposed • ver. 0.8.9 • 有名なDBMSに対応: ポスグレ, MySQL, Oracle, ...
  26. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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) } }
  27. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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) } } テーブル定義
  28. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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に接続
  29. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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) } } トランザクション
  30. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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) } } テーブル作成 テーブル削除
  31. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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) } } インサート
  32. SQL DSL object Teams: Table() { val id = long("id").autoIncrement().primaryKey()

    val name = varchar("name", length = 100) } fun main(args: Array<String>) { 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) } } 取得
  33. 一対多 関連 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 }
  34. 一対多 関連 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 } 外部キー設定
  35. 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
  36. Data Access Object object Teams: LongIdTable() { val name =

    varchar("name", length = 100) } class Team(id: EntityID<Long>): LongEntity(id) { companion object: LongEntityClass<Team>(Teams) var name by Teams.name } fun main(args: Array<String>) { 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}") } } }
  37. Data Access Object object Teams: LongIdTable() { val name =

    varchar("name", length = 100) } class Team(id: EntityID<Long>): LongEntity(id) { companion object: LongEntityClass<Team>(Teams) var name by Teams.name } fun main(args: Array<String>) { 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}") } } }
  38. Data Access Object object Teams: LongIdTable() { val name =

    varchar("name", length = 100) } class Team(id: EntityID<Long>): LongEntity(id) { companion object: LongEntityClass<Team>(Teams) var name by Teams.name } fun main(args: Array<String>) { 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}") } } }
  39. 一対多 関連 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<Long>): LongEntity(id) { companion object: LongEntityClass<Team>(Teams) var name by Teams.name val tasks by Tasks.referrersOn(Tasks.team) } class Task(id: EntityID<Long>): LongEntity(id) { companion object: LongEntityClass<Task>(Tasks) var content by Tasks.content var completedAt by Tasks.completedAt var team by Team.referencedOn(Tasks.team) }
  40. 一対多 関連 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<Long>): LongEntity(id) { companion object: LongEntityClass<Team>(Teams) var name by Teams.name val tasks by Tasks.referrersOn(Tasks.team) } class Task(id: EntityID<Long>): LongEntity(id) { companion object: LongEntityClass<Task>(Tasks) var content by Tasks.content var completedAt by Tasks.completedAt var team by Team.referencedOn(Tasks.team) } 関連を定義
  41. 取得 Task .find { Tasks.completedAt.isNull() } .forEach { task: Task

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

    -> val team: Team = task.team println("${team.name}: ${task.content}") } N + 1 のおそれ
  43. まとめ • JVMでさくっとWebアプリが作れる! • Spring ♡ Kotlin • アノテーションによる宣言→ラムダによるDSL •

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

    Exposedで簡単DB操作 今こそKotlinで Webアプリを!