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
はてなアカウント基盤 State of the Union
cockscomb
1
1.3k
壊れたパーサから始める関数型設計と構成的なパーサ
raiga0310
2
150
Honoでのサプライチェーン侵害対策 〜 3つのライブラリに学ぶ
yusukebe
7
1.7k
エンジニア向け会社紹介/Findy Company Profile
findyinc
6
360k
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
140
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
990
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
430
スマートグラスで並列バイブコーディング
hyshu
0
280
OS アップデート対応の取り組み方がもっと共有されてほしい
andpad
0
110
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
240
act1-costs.pdf
sumedhbala
0
170
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.6k
Featured
See All Featured
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
860
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.5k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
460
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Speed Design
sergeychernyshev
33
1.9k
GraphQLとの向き合い方2022年版
quramy
50
15k
Practical Orchestrator
shlominoach
191
11k
Paper Plane
katiecoart
PRO
1
52k
BBQ
matthewcrist
89
10k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
180
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
220
Exploring anti-patterns in Rails
aemeredith
3
440
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ͬΆ͍ίʔυ͕ॻ͚Δ • ຊ൪ར༻͑ΒΕͦ͏