Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
SpringBootとKtorを雑に比較してみる
Search
chiharu terashima
March 25, 2020
Programming
1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
SpringBootとKtorを雑に比較してみる
chiharu terashima
March 25, 2020
More Decks by chiharu terashima
See All by chiharu terashima
アイテムレビュー基盤で導入したアーキテクチャとその成果 / Item Review Introduction Architecture Outcome
chichi1091
1
3.6k
Other Decks in Programming
See All in Programming
30年振りにコンパイラの定数整数除算を改善した
herumi
2
490
複数の Claude Code が"放置"されてしまう問題をCLI ダッシュボードを自作して解決した話
sumihiro3
1
710
How I Won Prize Money at a Hackathon Using Codex and Symphony Alpha
yasei_no_otoko
0
110
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
490
運用ダッシュボードの設計を誰も教えてくれないのだけどみなさんどうしてるんですか? - チームに監視するという文化を根付かせるための第一歩を踏みたい -
satoshi256kbyte
1
140
React本体のコードリーディング
high_g_engineer
1
150
Built Our Own Background Agent at LayerX
layerx
PRO
10
5.9k
そこに3びきプロダクトがいるじゃろう——生成AI時代における“価値が届かない理由”の構造
kosuket
0
540
170k Jobs a Day on GKE: Scaling Mercari's CI Platform - and What's Next for AI-Native Development
junyaokabe
0
120
Go を使い始めて 2 ヶ月の学び / My first two months with Go
contour_gara
0
310
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
170
まだ間に合う!今年の夏こそSchemeのマクロ展開器を完全理解!
omasanori
0
160
Featured
See All Featured
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
490
Large-scale JavaScript Application Architecture
addyosmani
515
110k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
220
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.9k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.1k
Paper Plane
katiecoart
PRO
2
53k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
290
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
280
Ruling the World: When Life Gets Gamed
codingconduct
0
300
Leo the Paperboy
mayatellez
8
2.2k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
2
3.8k
Transcript
SpringBootͱKtorΛ ࡶʹൺֱͯ͠ΈΔ ͳ͕ͷJava #1 2020.03.25 @chichi1091
SpringBootͱKtorΛ ࡶʹൺֱͯ͠ΈΔ ͳ͕ͷJava #1 2020.03.25 @chichi1091
ࣗݾհ • ࣉౢઍʗͯͬ͠ʔʢ@chichi1091ʣ • גࣜձࣾHolmes • αʔόαΠυΤϯδχΞ • SpringBoot(Java,Kotlin),Groovy https://chichi1091.hatenablog.jp/
https://github.com/chichi1091
© Holmes,inc. All right reserved. ܖϚωδϝϯτγεςϜ ʮϗʔϜζΫϥυʯ ܖʹɺ Ϛωδϝϯτͷ ࢹΛɻ
© Holmes,inc. All right reserved. ։ൃڥ
։ൃʹؔΘΔશ৬छੵۃ࠾༻தͰ͢ʂ – ϓϩμΫτϚωʔδϟ – UI/UXσβΠφʔ – webϑϩϯτΤϯυΤϯδχΞ – αʔόαΠυΤϯδχΞ –
SRE
Ktorͱʁ
Ktorͱʁ • KotlinΛ։ൃ͍ͯ͠ΔJetBrains͕ࣾ։ൃͨܰ͠ྔ WebϑϨʔϜϫʔΫ • featureΛՃ͢Δ͜ͱͰ֦ுػೳͷར༻͕Մೳ • ςϯϓϨʔτΤϯδϯ • ೝূػೳ
• DI
SpringBootͱൺֱ
SpringBootͱൺֱ • ීஈ͍ͬͯΔSpringBootͱൺֱ͢Δ͜ͱͰཧղ ΛਂΊ͔ͨͬͨ • 100%KotlinͷϑϨʔϜϫʔΫʹڵຯ͕͋ͬͨ • ӡ༻ʹ͑ΒΕΔ͔ௐ͔ͨͬͨ
ΈࠐΈAPαʔό
ΈࠐΈAPαʔόʢSpringBootʣ • Buildπʔϧʹspring-boot-starter-xxxΛΈࠐΉ ͜ͱͰΓସ͕͑Մೳ • tomcat • jetty • undertow
ΈࠐΈAPαʔόʢKtorʣ • Buildπʔϧʹktor-server-xxxΛΈࠐΉ͜ͱͰ Γସ͕͑Մೳ • Netty • jetty • tomcat
BootRun
BootRun @SpringBootApplication @Controller class Application { @GetMapping("/") fun index(): String
= "Hello, Kotlin" } fun main(args: Array<String>) { runApplication<BlogApplication>(*args) } class Application fun main(args: Array<String>) { val server = embeddedServer(Netty, 8080) { routing { get("/") { call.respond(HttpStatusCode.OK,"Hello, Kotlin") } } } server.start() } 4QSJOH#PPU ,UPS
ςϯϓϨʔτΤϯδϯ
ςϯϓϨʔτΤϯδϯ • SpringBoot • Thymeleaf • Freemarker • Groovy Template
• Ktor • Thymeleaf • Freemarker • Velocity • Mustache • Kotlin DSLͰHTMLCSSΛॻ͘͜ͱՄೳ
ςϯϓϨʔτΤϯδϯʢKotlin DSLʣ get("/styles.css") { call.respondCss { body { backgroundColor =
Color.red } p { fontSize = 2.em } rule("p.myclass") { color = Color.blue } } } suspend inline fun ApplicationCall.respondCss(builder: CSSBuilder.() -> Unit) { this.respondText(CSSBuilder().apply(builder).toString(), ContentType.Text.CSS) } w ,PUMJOίʔυͰ$44Λੜɾग़ྗ
ςϯϓϨʔτΤϯδϯʢKotlin DSLʣ get("/") { call.respondHtml { head { link(rel =
"stylesheet", href = "/styles.css", type = "text/css") } body{ h1 { id = "title" +"ͳ͕ͷJava" } p("myclass") { +"myclass" } ul { for(n in 1..10) { li { onClick = "alert($n)" +"$n" } } } } } } w ,PUMJOίʔυͰ)5.-Λੜɾग़ྗ
Controller
ControllerʢSpringBootʣ @Controller //@RestController @RequestMapping("/users") class UsersApplication{ @GetMapping("/") fun list(): String
= "users routing ok" @GetMapping("/{id}") fun index(@PathVariable(“id") id: String): String = "user $id routing ok" }
ControllerʢKtorʣ fun main(args: Array<String>) { val server = embeddedServer(Netty, 8080)
{ routing { userController() } } server.start() } fun Route.userController() { route("/users") { get() { call.respondText { "users routing ok" } } get("/{id}") { val id = call.parameters["id"] call.respond(HttpStatusCode.OK, "user $id routing ok") } } } w SPVUFͰΤϯυϙΠϯτΛࢦఆ w ͦͷதʹϧʔςΟϯάઃఆ͠ΞΫγϣ ϯΛࢦఆ͢Δ
ྫ֎ϋϯυϥʔ
ControllerʢSpringBootʣ class ExceptionHandler: ResponseEntityExceptionHandler() { @ExceptionHandler(Exception::class) fun handleException(ex: Exception, headers:
HttpHeaders, request: WebRequest): ResponseEntity<Any> { return super.handleExceptionInternal(ex, "handleException", headers , HttpStatus.INTERNAL_SERVER_ERROR, request) } }
ControllerʢKtorʣ fun main() { val server = embeddedServer(Netty, port =
8082) { install(StatusPages) { exception<XxxException>{ cause -> call.respond(HttpStatusCode.InternalServerError) } exception<AuthorizationException>{ cause -> call.respond(HttpStatusCode.Forbidden) } } routing { userController() } } server.start() } class XxxException: RuntimeException() class AuthorizationException: RuntimeException() w ྫ֎ʹΑͬͯฦ͢εςʔλείʔυΛ ఆٛ
Session
SessionʢSpringBootʣ @Controller //@RestController @RequestMapping("/users") class UsersApplication{ @GetMapping("/") fun list(request: HttpServletRequest):
String { val session = request.session session.setAttribute("key", "name") val name = session.getAttribute("key") return "users routing ok" } } w Լهͷྫ4FTTJPOΛར༻͢Δํ๏ w ଞʹ w !4FTTJPO"UUSJCVUFT w !4DPQF ηογϣϯείʔϓ
SessionʢKtorʣ fun main() { val server = embeddedServer(Netty, port =
8082) { install(Sessions) { cookie<MySession>("MY_SESSION") { cookie.extensions["SameSite"] = "lax" } } routing { get() { val session: MySession = call.sessions.get<MySession>() ?: MySession("1") call.sessions.set("MY_SESSION", session.copy("2")) call.respondText { "users routing ${session.id}" } } } } server.start() } data class MySession ( val id: String ) w ʮ.:@4&44*0/ʯ͕$PPLJF໊
DBଓ
DBଓʢSpringBootʣ @Entity data class Users ( @Id @GeneratedValue(strategy=GenerationType.IDENTITY) val id:
Long, val name: String, val birthday: Date ) @Repository interface UsersRepository: JpaRepository<Users, Long> {} @RestController class UsersController( private val usersRepository: UsersRepository ) { @GetMapping(value = ["/"]) @ResponseStatus(HttpStatus.OK) fun index(): List<Users> = usersRepository.findAll() } • ORϚούʔJPA • τϥϯβΫγϣϯAOPͰ੍ޚ
DBଓʢKtorʣ • ORϚούʔExposed • ͪΖΜKotlin • JetBrains͕ࣾ։ൃ • BootRun࣌ʹH2ଓ fun
main(args: Array<String>) { // DBଓ Database.connect("jdbc:h2:mem:ktor_db;DB_CLOSE_DELAY=-1", "org.h2.Driver") val server = embeddedServer(Netty, 8080) { routing { userController() } } server.start() }
DBଓʢKtorʣ // Dao object Users : LongIdTable() { val name:
Column<String> = varchar("name", 50) val birthday: Column<DateTime> = date("birthday") } // Entity class User(id: EntityID<Long>): LongEntity(id) { companion object : LongEntityClass<User>(Users) var name by Users.name var birthday by Users.birthday } fun Route.userController() { val service by inject<UserService>() route("/users") { post() { SchemaUtils.create(Users) // ϚΠάϨʔγϣϯ val cal = Calendar.getInstance() cal.set(Calendar.YEAR, 2020) cal.set(Calendar.MONTH, 3 -1) cal.set(Calendar.DAY_OF_MONTH, 25) val today = DateTime(Calendar.getInstance().timeInMillis) transaction { User.new { name = "kotlin ktor" birthday = DateTime(today) } } call.respondText { "users insert" } } put() { transaction { Users.update ({ Users.id eq 1 }) { it[Users.name] = "kotlin OExposed" } } call.respondText { "users update" } } delete() { transaction { Users.deleteWhere { Users.id eq 1 } } call.respondText { "users delete" } } get() { var result: List<User> = listOf() transaction { result = User.all().sortedByDescending{ it.birthday } } call.respondText { "users routing ${service.getName()}" } } } } w 42-ൃߦ࣌USBOTBDUJPOඞਢ w ݕࡧଞʹ val user: SizedIterable<User> = User.find { Users.id eq 1 } val user: User? = User.findById(1L)
JSON
JSONʢSpringBootʣ @RestController @RequestMapping("/users") class UsersApplication{ @GetMapping("/") fun list(request: HttpServletRequest): ResponseEntity<Json>
{ val json = Json(1,"Kotlin") return ResponseEntity.of(Optional.of(json)) } } data class Json( private val id: Int, private val name: String )
JSONʢKtorʣ fun main() { val server = embeddedServer(Netty, port =
8082) { install(ContentNegotiation) { jackson { configure(SerializationFeature.INDENT_OUTPUT, true) } } routing { get() { call.respond(Json(1, "Ktor")) } } } server.start() } data class Json( private val id: Int, private val name: String ) w JOTUBMMͰ+BDLTPOͷઃఆ w ฦ٫͞ΕΔ+40/Λܗ
DI
DIʢSpringBootʣ @Service class UserService { fun getName(): String { return
"Kotlin" } } @RestController class UserController( private val userService: UserService ) { @GetMapping("/") fun index(): String { return "Hello, ${userService.getName()}" } }
DIʢKtorʣ • KtorͰDI͢ΔʹʮKodeinʯ͔ʮKoinʯΛ͏ • ࠓճKoin fun main() { val server
= embeddedServer(Netty, port = 8082) { install(Koin) { modules(module) } routing { userController() } } server.start() } val module = module(createdAtStart = true) { singleBy<UserService, UserService>() } class UserService { fun getName(): String{ return "Kotlin" } } fun Route.userController() { val service by inject<UserService>() route("/users") { get() { call.respondText { "users routing ${service.getName()}" } } } } w JOTUBMM ,PJO Ͱ%*ίϯςφొ w γϯάϧτϯͰ6TFS4FSWJDFΛొ w JOKFDUͰऔಘ
·ͱΊ
·ͱΊ • SpringBootͱಉͷfeature͕ἧ͍ͬͯΔΑ͏ʹ ײͨ͡ • υΩϡϝϯτΘ͔Γ͍͢(https://ktor.io/) • Githubͷߋ৽׆ൃ • featureͲΜͲΜ૿͑ͦ͏ʁ
• SpringBootΑΓKotlinͬΆ͍ίʔυ͕ॻ͚Δ • ຊ൪ར༻͑ΒΕͦ͏