Slide 1

Slide 1 text

ɹɹɹɹ Server-Side Kotlin By UPSIDERɹਗ਼ਫݦ

Slide 2

Slide 2 text

SELF INTRODUCTION • ਗ਼ਫݦ(@kaonash_) • Lead Software Engineer @ UPSIDER • Ҡॅ͍ͨ͠ܥΤϯδχΞʢ2022೥ɺ௕໺ʹҠॅ༧ఆʣ • Love: ୌ / Kitkat / Kotlin

Slide 3

Slide 3 text

SELF INTRODUCTION UZABASEɿاۀɾۀք෼ੳϓϥοτϑΥʔϜͷ։ൃɻServer-side Javaɻ ↓ NewsPicksʢࣾ಺స੶ʣɿܦࡁχϡʔεΞϓϦͷ։ൃɻServer-side͸JavaɺAndroidΞϓϦ։ൃ ͰKotlinॳମݧɻ ↓ Bizreachɿ৽نࣄۀͱͯ͠ैۀһσʔλϕʔεαʔϏεͷ։ൃɻServer-side Kotlin(Spring Boot) ↓ UPSIDERɿ૑ۀҰ೥ͷελʔτΞοϓʹͯB to BͷFinTechϓϩμΫτΛ։ൃɻServer-side Kotlin(Ktor)

Slide 4

Slide 4 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 5

Slide 5 text

ࠓ೔࿩͞ͳ͍͜ͱ • Ktorͷৄ͍͠࢓૊Έ • ຊ൪ӡ༻ͯ͠Έͨײ૝ʢ·ͩϦϦʔεલͳͷͰɾɾɾʣ

Slide 6

Slide 6 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 7

Slide 7 text

Server-Side Kotlinͬͯ࢖͑Δͷʁ ɾJavaΛ࠾༻ͨ͠έʔεͱൺ΂ͯ΋શવҧ࿨ײͳ͍ ɹɹɹͱ͍͏͔ਖ਼௚ɺҰ౓Kotlinʹ׳ΕͪΌ͏ͱ΋͏JavaͰॻ͖ͨ͘ͳ͘ͳΔ ɾܕਪ࿦΍֦ுؔ਺ͳͲͷ͓͔͛ͰඇৗʹεοΩϦ͔͚Δ ɾnull-safety࠷ߴ ɾJava੡ͷϥΠϒϥϦ͕࢖͑ΔͨΊɺΤίγεςϜʹ͍ͭͯ΋৺഑ෆཁ ɾͨͩ͠ɺJava੡ϥΠϒϥϦར༻࣌ʹ͸མͱ͕݀͠ɾɾɾ

Slide 8

Slide 8 text

Java੡ϥΠϒϥϦར༻࣌ͷ஫ҙ఺ ɾJavaͷϝιουΛݺͼग़͢ͱɺ໭Γ஋͸T!ܕ (=T΋͘͠͸T?)ʹͳΔ ɾϥΠϒϥϦଆͰରԠͯ͠ͳ͍ͱnull-safetyͷੈք͕յΕΔ val message = "test" val encrypted = StringEncrypter.encrypt(message) // StringEncrypter͕JavaϥΠϒϥϦͩͱ println(encrypted.length) // ͜͜Ͱ͵ΔΆʹͳΔ͔΋

Slide 9

Slide 9 text

ʮ͔ͤͬ͘Kotlinʹͨ͠ͷʹɾɾɾʯ ײ͸ਖ਼௚ͪΐͬͱ͋Δ

Slide 10

Slide 10 text

Kotlin੡ϥΠϒϥϦͳΒ҆৺ʂ

Slide 11

Slide 11 text

Ͱ΋Server-sideͰ࢖͑ΔΑ͏ͳ Kotlin੡ͷϥΠϒϥϦͳΜͯɾɾɾ

Slide 12

Slide 12 text

ࠓΊͬͪΌ૿͑ͯ·͢ʂ

Slide 13

Slide 13 text

• Language : Kotlin • WebϑϨʔϜϫʔΫ : Ktor server • O/R Mapper : Exposed • DI : Koin • Http client : Ktor client • JSON Parser : Jackson • Testing : JUnit5 / AssertJ / MockK ฐࣾͷٕज़ελοΫ Server ΄΅Pure Kotlin!

Slide 14

Slide 14 text

Server-side Kotlin͸ʮͰ͖Δʁʯ͔Β ʮԿΛબ΂͹͍͍ʁʯʮͲ͏΍ͬͨΒ͍͍ʁʯ΁

Slide 15

Slide 15 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 16

Slide 16 text

WHAT IS • JetBrains͕։ൃͨܰ͠ྔɾඇಉظWebϑϨʔϜϫʔΫ • ಡΈํ͸ʮ͚͍ͨʔʯʢ࠷ॳ͸ʮͨ͜ʔʯͩͱࢥͬͯͨʣ • Pure Kotlin • PluggableʹඞཁͳػೳͷΈΛ௥Ճ͍͚ͯ͠Δ • 2018೥11݄ʹ1.0͕ϦϦʔεʂʢݱࡏͷ࠷৽͸1.2.3ʣ • Server͚ͩ͡Όͳͯ͘Client΋͋Δ https://ktor.io/clients/index.html

Slide 17

Slide 17 text

Spring Bootͱͷ˒ͷࠩ ·ͩ·ͩ͜Ε͔Β͚ͩͲɺ͍ͩͿ஫໨͕ू·͖ͬͯͯΔ

Slide 18

Slide 18 text

ͳͥKtorΛબΜͩͷ͔ʁ

Slide 19

Slide 19 text

ٕज़બఆΛͨ͠λΠϛϯάɿ2018೥9݄

Slide 20

Slide 20 text

ީิ • SpringBoot • Jooby • Ktor

Slide 21

Slide 21 text

SpringBoot • ͍Θͣͱ஌ΕͨJavaքͷσϑΝΫτελϯμʔυɻServer-side KotlinͰ΋࠾༻ࣄྫ͕ଟ͘ଘࡏɻ • KotlinରԠ • લ৬ʢ౰࣌͸μϒϧϫʔΫͩͬͨͷͰݱ৬ʣ΋SpringBootͰServer-Side Kotlin΍ͬͯͨͷͰܦݧ͸͋ Δ • શ෦ೖΓͰศརͳͷ͕ͩͦͷ෼ىಈ͕࣌ؒ௕͘ɺͦͷؒʹTwitterͱ͔Twitterͱ͔Twitterͱ͔Λݟͨ Γͯ͠͠·͏ • Ξϊςʔγϣϯ஍ࠈ͕͋·Γ޷͖ʹͳΕͳ͍ɻɻɻʢݸਓͷײ૝Ͱ͢ʣ • ΫϦʔϯΞʔΩςΫνϟΛݕ౼͍ͯͨͨ͠Ίɺશମ͕ΨοπϦSpringʹґଘ͢Δͷ͕ͪΐͬͱ໰୊

Slide 22

Slide 22 text

࣮ࡍͷΞʔΩςΫνϟ ΫϦʔϯΞʔΩςΫνϟͷߟ͑Ͱ͸WebϑϨʔϜϫʔΫ͸ʮৄࡉʯͰ͋ΓϏδωεϩ δοΫ͔Β੾Γ཭͢΂͖΋ͷɻ Ͱ΋SpringBootͩͱDI΍Β@transactional΍ΒͰશମ͕ΨοπϦSpringʹґଘͯ͠͠·͏ɻ

Slide 23

Slide 23 text

Jooby • ܰྔߴ଎ɺPluggableͳJava੡ϚΠΫϩϑϨʔϜϫʔΫɻ ࢥ૝͕Ktorͱ͔ͳΓ͍ۙɻ • KotlinରԠ • ଞࣾͷ࠾༻ࣄྫ΋͋Δ • ࠷ॳ͸ୈҰީิͩͬͨ

Slide 24

Slide 24 text

Ktor • ٕज़બఆ౰࣌ͷόʔδϣϯ͸0.9.5 • JetBrains੡ͱ͍͏͜ͱͰɺࠓޙ΋ϝϯς͞ΕΔͩΖ͏ͱ͍͏҆৺ײ • GithubͷIssuesΛνΣοΫͯ͠Έ͕ͨɺServerଆͰ͸͋·Γେ͖ͳ໰୊͸࢒ͬͯͳ͞ ͦ͏ • ʮ0.9ͷ࣍͸1.0͡ΌͶʁʯˠ݁Ռతʹ͸౰ͨͬͯͨ • Jooby΋͍͍͕ɺͲ͏ͤͳΒPure KotlinͷϑϨʔϜϫʔΫΛ࢖ͬͯΈ͍ͨ • ʮૣΊʹ࠾༻͢Ε͹ɺٕज़తʹ஫໨ΛूΊͯ࠾༻ͱ͔Ͱ΋༗རͦ͏ʯͱ͍͏ଧࢉ

Slide 25

Slide 25 text

݁࿦ ʮͳΜ͔Ktor࢖ͬͯΈ͍ͨʯ

Slide 26

Slide 26 text

࣮ࡍ࢖ͬͯΈͯͷײ૝ • ىಈૣ͍ͷ࠷&ߴʢ.ktϑΝΠϧ͕300ݸ͘Β͍ͷঢ়ଶͰϏϧ υऴΘ͔ͬͯΒ1.5ඵ͘Β͍Ͱىಈ׬ྃʣ • ඞཁͳ΋ͷ͸Ұ௨Γἧ͍ͬͯΔ • ֦ுؔ਺ɾߴ֊ؔ਺͕;ΜͩΜʹ࢖ΘΕ͍ͯΔͷͰɺ׳Ε ͯͳ͍ਓ͸࠷ॳͪΐͬͱށ࿭͏͔΋ʁ • શମͱͯ͠͸ͱͯ΋ຬ଍

Slide 27

Slide 27 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 28

Slide 28 text

ͳʹ͸ͱ΋͋ΕHello World

Slide 29

Slide 29 text

Hello World! Ktor serverͷىಈํ๏͸͍͔ͭ͋͘Δ͕ɺҰ൪؆୯ͳํ๏ fun main() { embeddedServer(Netty, 8080) { install(Routing) { get("/") { call.respondText("Hello World!") } } }.start() }

Slide 30

Slide 30 text

Hello World! fun main() { embeddedServer(Netty, 8080) { install(Routing) { get("/") { call.respondText("Hello World!") } } }.start() } NettyΛར༻ͯ͠ΞϓϦέʔγϣϯΛ࡞੒ port൪߸͸8080 Get methodͰpath"/"͕ݺͼग़͞ΕͨΒ responseͱͯ͠"Hello World!"ͱ͍͏จࣈྻΛฦ٫͢Δ ͜ͷblockͰroutingΛఆٛ

Slide 31

Slide 31 text

ΑΓॊೈͳىಈํ๏

Slide 32

Slide 32 text

EngineMain

Slide 33

Slide 33 text

EngineMain • EngineMain͸Ktor͕༻ҙͨ͠Ϋϥε • ར༻͢ΔEngineʹΑͬͯ4छྨ༻ҙ͞Ε͍ͯΔ • io.ktor.server.cio.EngineMain • io.ktor.server.tomcat.EngineMain • io.ktor.server.jetty.EngineMain • io.ktor.server.netty.EngineMain •mainؔ਺Λ͍࣋ͬͯΔͷͰɺαʔόʔىಈ࣌͸͜ͷϝιουΛ࣮ߦ •ىಈ࣌ʹࣗಈతʹapplication.confͱ͍͏ઃఆϑΝΠϧΛಡΈࠐΈɺͦͷઃఆ ಺༰ʹΑͬͯىಈ࣌ͷॲཧΛܾఆ

Slide 34

Slide 34 text

EngineMain • ར༻͢Δػೳ͸ɺApplicationΫϥεͷ֦ுؔ਺ʹͯఆٛ͢Δ fun Application.module() { install(Routing) { get("/") { call.respondText("Hello World!") } } } Application.kt ͜ͷϝιου໊͸ͳΜͰ΋͍͍ installϝιουΛ௥Ճ͍ͯ͘͜͠ͱͰɺඞཁͳػೳΛ޷͖ʹ૊ΈࠐΊΔ

Slide 35

Slide 35 text

EngineMain • ઃఆϑΝΠϧapplication.confʹɺىಈͷઃఆ৘ใΛهड़ ktor { deployment { port = 8080 } application { modules = [ ApplicationKt.module ] } } application.conf (ύοέʔδ໊.)Ϋϥε໊(ϑΝΠϧ໊ͷ຤ඌʹKtΛ͚ͭΔ).֦ுؔ਺໊

Slide 36

Slide 36 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 37

Slide 37 text

Routing • RoutingΛinstall͢Δ͜ͱͰɺrouteొ࿥͕Ͱ͖Δ • call.parameters͔ΒϦΫΤετύϥϝʔλ΍ύεύϥϝʔλ ΛऔಘՄೳʢͨͩ͠ܕ͸͢΂ͯStringʹͳΔʣ fun Application.module() { install(Routing) { get("/users/{userId}") { // ܕ͸ඞͣStringͳͷͰඞཁʹԠͯ͡Ωϟετ͢Δ val userId = call.parameters["userId"].toLong() } } }

Slide 38

Slide 38 text

΋ͬͱλΠϓηʔϑʹ ύϥϝʔλʔΛѻ͍͍ͨ

Slide 39

Slide 39 text

Locations

Slide 40

Slide 40 text

Locations(※ݱࡏ͸·ͩexperimental) • ύϥϝʔλΛλΠϓηʔϑʹѻ͑Δ • Int, Long, Float, Double, Boolean, String, enum͕࢖༻Մೳ @Location("/users/{userId}") data class UserLocation(val userId: Long) fun Application.module() { install(Locations) install(Routing) { get { param -> // param͸UserLocationܕʹͳΔͷͰɺuserIdΛͦͷ··LongܕͰऔಘͰ͖Δ val userId = param.userId } } }

Slide 41

Slide 41 text

Locationͷωετ΋Ͱ͖Δ @Location("/users/{userId}") data class UserLocation(val userId: Long) { @Location("/email") data class EmailLocation(val userLocation: UserLocation) } • ؔ࿈ͨ͠routeΛ·ͱΊΒΕΔͷ͸ϝϦοτ͕ͩɺ਺͕૿͑ͯ͘ Δͱͪ͝Όͪ͝Όͯ͠Θ͔ΓͮΒ͘ͳΔ • ʮKtorͷRoutingఆٛͷϕετϓϥΫςΟεΛߟ͑ͯΈΔʯʹৄ ͘͠ॻ͖·ͨ͠ʢͪͳΈʹલ৬ͷTechϒϩάʣ
 https://tech.bizreach.co.jp/posts/324/ktor-routing/

Slide 42

Slide 42 text

΋͏Ұาલ΁

Slide 43

Slide 43 text

ϓϦϛςΟϒҎ֎ͷܕ΁ͷࣗಈม׵

Slide 44

Slide 44 text

ͨͱ͑͹ValueObject • ͜Μͳײ͡ͰɺValueObject΋ࣗಈͰม׵Ͱ͖Δͱศར data class UserId(val userId: Long) @Location("/users/{userId}") data class UserLocation(val userId: UserId) fun Application.module() { install(Locations) install(Routing) { get { param -> val userId = param.userId // UserIdܕͰͦͷ··औಘͰ͖Δʂ } } }

Slide 45

Slide 45 text

DataConversionΛ࢖͏ͱͰ͖·͢ fun Application.module() { install(DataConversion) { convert { decode { values, _ -> values.singleOrNull()?.let { UserId(it.toLong()) } } encode { id -> when (id) { is UserId -> listOf(id.value.toString()) else -> throw DataConversionException("Can not convert.") } } } } }

Slide 46

Slide 46 text

ͿͬͪΌ͚ɺSpringBootͱൺ΂ͯͲ͏ʁ • ֤Controllerʹ͚ͭΔ@RequestMapping ΞϊςʔγϣϯʹrouteΛهࡌ͢Δํࣜ • ਺͕૿͑Δͱʮ͜ͷAPIɺͲ͜ͷ ControllerݺΜͰΔΜ͚ͩͬʯ͕୳ͮ͠ Β͍໰୊ SpringBoot Ktor • routeΛ·ͱΊͯఆٛ͢Δํࣜ • LocationsΛ࢖Θͳ͍ͱλΠϓηʔϑʹ ѻ͑ͳͯͭ͘ΒΈ • Locations͸ఆٛͷ࢓ํΛؾΛ͚ͭͳ͍ ͱΊͬͪΌಡΈͮΒ͘ͳΔ Ұ௕Ұ୹ɻݸਓతʹ͸Ktorͷ΍Γํͷ΄͏͕޷͖͕ͩɺ Locationsͷ࢖͍উखΛ΋͏গ͠վળͯ͠΄͍͠

Slide 47

Slide 47 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 48

Slide 48 text

ೝূॲཧ • ϕʔγοΫೝূ / Digestೝূ / JWT / LDAPೝূ / OAuthΛαϙʔτɻ • install(Authentication)͢Ε͹ར༻Մೳʢ௨ৗͷktorҎ֎ʹktor_authΛ dependenciesʹ௥Ճ͢Δ͜ͱ͕ඞཁʣ • Routingఆٛʹͯೝূ͕ඞཁͳrouteʹରͯ͠authenticate{}ϒϩοΫͰғΉͱɺ ೝূ͕௨͍ͬͯΔ৔߹͚ͩॲཧΛ࣮ߦ͢Δ͜ͱ͕Մೳɻ

Slide 49

Slide 49 text

Routingଆ fun Application.module() { install(Routing) { // ͜͜͸ೝূෆཁ post(“/login") { // ϩάΠϯॲཧɻ੒ޭ࣌ʹ͸tokenΛੜ੒ͯ͠ฦ٫ } authenticate { // ͜ͷϒϩοΫ಺͸ೝূ͕௨ͬͯͳ͍ͱ࣮ߦͰ͖ͳ͍ get("/users/{userId}") { // ͜ΕͰϩάΠϯϢʔβʔͷ৘ใΛऔಘͰ͖Δ val user = authentication.principal()!! ... } } } }

Slide 50

Slide 50 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 51

Slide 51 text

DIʹ͍ͭͯ • DIࣗମ͸Ktorͷػೳͱͯ͠ఏڙ͞Ε͍ͯͳ͍ͷͰɺ޷͖ͳϥ ΠϒϥϦΛ࢖͍͍ͬͯ • KtorͷެࣜϖʔδʹKodeinΛ࢖ͬͨαϯϓϧ͕͋Δ • Koin͸ࣗલͰKtor༻ʹApplication.installͰ͖ΔFeatureΛఏڙ͠ ͓ͯΓɺ͜ΕΛ࢖͏ͱKtorىಈ࣌ʹࣗಈͰDIίϯςφΛى ಈͯ͘͠ΕΔ

Slide 52

Slide 52 text

KoinΛ࢖ͬͨྫ fun Application.module() { install(Koin) { modules( module { single { UserController() } } ) } install(Routing) { val userController: UserController by inject() get("users") { call.respond(userController.find()) } } } ͜ͷதͰDI͢ΔϞδϡʔϧΛఆ͓ٛͯ͘͠ͱ औಘॲཧΛinject()ʹҠৡ͢Δ͜ͱͰΦϒδΣΫτ͕औಘͰ͖Δʂ

Slide 53

Slide 53 text

AGENDA • Server-Side Kotlinͷݱঢ় • ٕज़બఆͷϙΠϯτɿͳͥKtorΛબΜͩͷ͔ʁ • ࣮ફKtor • ىಈͷํ๏ • Routing • ೝূ • DI • Testing

Slide 54

Slide 54 text

TestEngine • Ktorʹ༻ҙ͞Ε͍ͯΔɺԾ૝తʹαʔόʔΛىಈ͢ΔͨΊͷςετ༻ Engine • ىಈ࣌͸withTestApplication()Λ࢖༻͢Δ • ࣮ࡍͷHTTPϦΫΤετ͸ड͚෇͚ͳ͍͕ɺٙࣅϦΫΤετΛඈ͹͢͜ͱ ʹΑͬͯఆٛͨ͠routeΛݺͼग़͢͜ͱ͕Ͱ͖Δ

Slide 55

Slide 55 text

TestEngineͷ࢖༻ྫ withTestApplication(Application::module) { handleRequest(HttpMethod.Post, "/login") { addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString()) setBody(listOf( "email" to "[email protected]", "password" to "loginPass" ).formUrlEncode()) }.apply { assertThat(response.status()).isEqualTo(HttpStatusCode.OK) } } ىಈ͢ΔΞϓϦέʔγϣϯͷϝιουΛࢦఆ Httpϝιουͱpathͷࢦఆ HeaderͱBodyͷࢦఆ handleRequestͷ໭Γ஋TestApplicationCallܕ͕ ϓϩύςΟͱͯ͠responseΛอ͍࣋ͯ͠Δ

Slide 56

Slide 56 text

MockKͱKoinΛ࢖ͬͨMocking

Slide 57

Slide 57 text

ͨͱ͑͹ɺ͜͏͍͏ॲཧΛ୯ମͰςετ͢Δʹ͸ʁ install(Routing) { val userController: UserController by inject() @Location("/users/{userId}") data class UserLocation(val userId: UserId) get { call.respond(userController.find(userId)) } } ͍ͭ͜ΛMockԽ͍ͨ͠

Slide 58

Slide 58 text

MockKͱKoin val userController = mockk(relaxed = true) val module = module { single { userController } } @Test fun `Return user data when called request with user id`() { val userId = UserId(1) every { userController.find(userId) } returns UserData(1, "ࢁాଠ࿠") withTestApplication(Application::module) { startKoin { modules(module) } handleRequest(HttpMethod.Get, "/users/1").apply { assertThat(response.status()).isEqualTo(HttpStatusCode.OK) … } } } MockΦϒδΣΫτΛ࡞੒͠ɺKoinϞδϡʔϧʹొ࿥ MockΦϒδΣΫτ͕ݺ͹Εͨ࣌ͷڍಈΛఆٛ KoinΛىಈ

Slide 59

Slide 59 text

׬ᘳɾɾɾͱࢥ͍͖΍

Slide 60

Slide 60 text

ౖΒΕͨ

Slide 61

Slide 61 text

ͳΜͰʁ • Application.moduleͰinstall(Koin)͢Δͱɺىಈ࣌ʹࣗಈతʹstartKoin͕ݺ͹ΕΔ fun Application.module() { install(Koin) { modules( module { single { UserController() } } ) } } • TestEngineͰ΋Ծ૝తʹKtorΛىಈ͍ͯ͠ΔͨΊɺಉ͡Α͏ʹstartKoin͕૸Δ • MockΦϒδΣΫτΛΠϯδΣΫτ͢ΔͨΊʹstartKoin͢ΔͱೋॏىಈʹͳΔ

Slide 62

Slide 62 text

ͱ͍͏͜ͱͰɺ1ߦ௥Ճ val userController = mockk(relaxed = true) val module = module { single { userController } } @Test fun `Return user data when called request with user id`() { val userId = UserId(1) every { userController.find(userId) } returns UserData(1, "ࢁాଠ࿠") withTestApplication(Application::module) { stopKoin() startKoin { modules(module) } handleRequest(HttpMethod.Get, "/users/1").apply { assertThat(response.status()).isEqualTo(HttpStatusCode.OK) … } } }

Slide 63

Slide 63 text

࠶࣮ߦ

Slide 64

Slide 64 text

Let’s enjoy Server-Side Kotlin and Ktor!!

Slide 65

Slide 65 text

࠷ޙʹએ఻

Slide 66

Slide 66 text

WE ARE HIRING!! • Server side: Kotlin(Ktor) • Front: Nuxt.js, Typescript • Backend: Go • ࣄۀ಺༰ɿ๏ਓ޲͚VISAΧʔυΛ࢖ͬͨBtoBαʔϏεʢৄࡉ͸ۙ೔ެ։ʣ • ෭ۀ׻ܴ(Քಇि2ఔ౓ʙOKʣ • ϑϧϦϞʔτOKʢΦϑΟε͸࿡ຊ໦ʣ • ͪΐͬͱͰ΋ؾʹͳΔਓ͸࠙਌ձɺ΋͘͠͸Twitterʹͯ@kaonash_·Ͱʂ

Slide 67

Slide 67 text

Thank you!!