Slide 1

Slide 1 text

,PUMJOͰѪͰΔ .JDSPTFSWJDFT !TUPSNDBU ,PUMJO'FTULPUMJOGFTU

Slide 2

Slide 2 text

XIPJT ‣ $ZCFS"HFOU *OD ‣ '3&4)-*7&5FDI-FBE ‣ IUUQTCMPHTUPSNDBUJP TUPSNDBU

Slide 3

Slide 3 text

%PDLFS,VCFSOFUFT ࣮ફίϯςφ։ൃೖ໳ ຊ೔ൃച "NB[PO,JOEMF (JIZP%JHJUBM1VCMJTIJOH ָఱϒοΫεɺϤυόγυοτίϜ )POUPɺ%..DPNFUD ʢٕज़ධ࿦ࣾʣ ˞෺ཧຊɺॳ೔Ͱ"NB[POೖՙ଴ͪ

Slide 4

Slide 4 text

ϫΠͱ,PUMJO

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

IUUQTTQFBLFSEFDLDPNTUPSNDBUTQBSLGSBNFXPSLXJUILPUMJO

Slide 7

Slide 7 text

IUUQTTQFBLFSEFDLDPNTUPSNDBUUPVSPGCVJMETZTUFNJOLPUMJO

Slide 8

Slide 8 text

IUUQTTQFBLFSEFDLDPNTUPSNDBUOBUJWF

Slide 9

Slide 9 text

4QFDJBM5IBOLT ‣ IUUQCMPHTPVTIJNF ‣ ϒϩάͰαʔόαΠυ,PUMJO΍HSQDKBWBͷ஌ݟଟ਺ ‣ '3&4)-*7&ͷαʔόαΠυ,PUMJOΛਪਐ ‣ ݱࡏ͸"OESPJE΍ͬͯΔ ‣ ࠓ೔͸Ո଒αʔϏεʢΩϟϯϓʣ⛺Ͱෆࡏ TPVTIJO TPVTIJ@OP[BXB

Slide 10

Slide 10 text

͕ጪґͨͭ͠΋ΓͰ࿩͠·͢ ˞ጪґϓϨθϯ

Slide 11

Slide 11 text

4FSWFS4JEF,PUMJOͯ͠Δͻͱ

Slide 12

Slide 12 text

4FSWFS4JEF,PUMJO΍͍͖ͬͯ ͱ͍͏ϞνϕʔγϣϯΛߴΊͯؼͬͯ΄͍͠

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

‣ ϥΠϒ഑৴αʔϏεʢʙʣ ‣ υϝΠϯมߋճɺαʔϏε໊มߋճ ‣ .JDSPTFSWJDFT"SDIJUFDUVSF ‣ "NB[PO&$4,VCFSOFUFTPO"84

Slide 15

Slide 15 text

.JDSPTFSWJDFTͷཧ༝ ‣ Մ༻ੑͷ޲্ͱো֐ͷہॴԽ ‣ Ұ෦͕յΕͯ΋ɺΞϓϦέʔγϣϯશମͱͯ͠͸ΫϦςΟ ΧϧϛογϣϯΛ਱ߦͰ͖ΔΑ͏ʹ ‣ 4FSWJDF͝ͱʹҧ͏ݴޠɾٕज़Λར༻Ͱ͖Δ ‣ Ұ෦ͷ௅ઓతͳٕज़ͷ౤ೖ ‣ ΤϯδχΞͷϞνϕʔγϣϯʢ͜Εେ͖͍ʣ ‣ ΋ͪΖΜେมͳ͜ͱ΋͋Δ

Slide 16

Slide 16 text

‣ (PMBOH ,PUMJOͷϋΠϒϦουߏ੒ ‣ ഑৴γεςϜɺ௿ϨΠϠʔܥ͸(PMBOH ‣ ৽نͷ"1*͸جຊ,PUMJOͰ ‣ 3&45H31$ ‣ Ұ෦QVCMJDͳH31$ΤϯυϙΠϯτ͕͋Δ ݱࡏͷ'3&4)-*7&

Slide 17

Slide 17 text

"SDIJUFDUVSF

Slide 18

Slide 18 text

,PUMJO࠾༻ͷഎܠ ‣ ࠷ॳ͸(PҰล౗ͩͬͨ ‣ "1*Λ(PͰ͍ͬͺ͍ॻ͍ͯ͘ͷ͸πϥΠʢے೑௧ʣ ‣ .JDSPTFSWJDFTͩ͠ɺݴޠʹറΒΕͳ͍͍ͯ͘͡Όͳ͍͔

Slide 19

Slide 19 text

ϝϯόʔͷ৺ͷڣͼ ‣ /VMM҆શʂ ‣ ߴ֊ؔ਺΄͍͠ʂ ‣ ֦ுؔ਺ॻ͖͍ͨʂ ‣ EBUBDMBTT ‣ ΢ν+BWB͔͚Δਓଟ͍ʂʢ͔͠͠+BWB͸ͳ͍Αͳʣ ‣ +BWB͔ͩΒH31$࢖͑Δ ‣ ,PUMJO͔Θ͍͍Α,PUMJO

Slide 20

Slide 20 text

(P΋޷͖Ͱ͢Αʁ ɹɹɹɹɹɹɹɹɹʢదࡐదॴେࣄʣ

Slide 21

Slide 21 text

։ൃํ਑ ‣ "1*Ͱ͋Ε͹جຊ,PUMJOͰ ‣ "1*Ͱ΋(Pͱ਌࿨ੑͷߴ͍ྖҬͰ͋Ε͹(P ‣ ௿ϨΠϠʔɺಈը഑৴ؔ࿈ɺಠࣗϛυϧ΢ΣΞ౳ͷόοΫ ΤϯυγεςϜ͸Ҿ͖ଓ͖(PͰ ‣ ೥ळ͔ΒҰ෦.JDSPTFSWJDFTͰ,PUMJO౤ೖ։࢝ ‣ ݱࡏɺ,PUMJO੡ͷ4FSWJDF͕໿ݸՔಇ

Slide 22

Slide 22 text

࠾༻͍ͯ͠Δ'SBNFXPSL ‣ 4QSJOH#PPU ‣ 4QBSL'SBNFXPSL

Slide 23

Slide 23 text

4QSJOH#PPU

Slide 24

Slide 24 text

4UBDL ‣ 4QSJOH#PPU4QSJOH'SBNFXPSL ‣ HSQDKBWB ‣ +BDLTPO ‣ %PNB ‣ 3&45ͱH31$྆ํ ‣ LPUMJOYDPSPVUJOF

Slide 25

Slide 25 text

3&45GVM"1* ‣ 3PVUFS'VODUJPO%4- ‣ 4QSJOHͷ,PUMJOαϙʔτͰొ৔ ‣ ैདྷͷ.7$ͷΑ͏ʹ"OOPUBUJPOଟ༻Ͱ͸ͳ͘ɺ,PUMJOͷ%4- Ͱॻ͘

Slide 26

Slide 26 text

3PVUFS'VODUJPO%4- @Configuration class TaskRoutes(private val taskHandler: TaskHandler, private val exceptionFilter: ExceptionFilter) { @Bean fun taskRouter() = router { (accept(APPLICATION_JSON) and "/api").nest { "/task".nest { POST("/", taskHandler::create) GET("/{id}", taskHandler::fetchByTaskId) PUT("/{id}", taskHandler::updateByTaskId) DELETE("/{id}", taskHandler::deleteByTaskId) PUT("/{id}/finish", taskHandler::finishByTaskId) } "/tasks".nest { GET("/", taskHandler::fetchAll) } } }.filter(exceptionFilter()) } 4QSJOH.7$ʹൺ΂ͯγϡοͱ͢Δ Ξϊςʔγϣϯ஍ࠈ͔Β୤٫ ,PUMJOͰ͞Βʹγϡοͱ͢Δ

Slide 27

Slide 27 text

8FC5FTU$MJFOU @RunWith(SpringRunner::class) class TaskRoutesTest { lateinit var client : WebTestClient lateinit var taskHandler: TaskHandler lateinit var exceptionFilter: ExceptionFilter val mapper = ObjectMapper().registerModule(KotlinModule()) @Before fun before() { taskHandler = mock(TaskHandler::class) exceptionFilter = ExceptionFilter() val taskRoutes = TaskRoutes(taskHandler, exceptionFilter) client = WebTestClient.bindToRouterFunction(taskRoutes.taskRouter()).build() } ɾɾɾ } 8FC5FTU$MJFOUͰ3PVUFS'VODUJPOͷςετ

Slide 28

Slide 28 text

8FC5FTU$MJFOU @Test fun `GET Task`() { // mock `when`(taskHandler.fetchByTaskId(any())).thenReturn(ok().json().body(Mono.just(mockModel))) client.get().uri("/api/task/1") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange() .expectStatus().isOk .expectBody() .consumeAsStringWith { val actual = mapper.readValue(it, TaskModel::class) actual.id shouldBe 1L actual.title shouldBe “kotlin fest" actual.createdAt shouldBe "2018-08-25T16:30:00Z" actual.updatedAt shouldBe "2018-08-25T16:30:00Z" } }

Slide 29

Slide 29 text

H31$ ‣ 3&45GVM"1*ͷଞʹH31$ͷΤϯυϙΠϯτ΋༻ҙ ‣ (PPHMF͕։ൃͨ͠31$ϓϩτίϧͷҰͭ ‣ )551ϕʔε ‣ σʔλϑΥʔϚοτ͸1SPUPDPM#VGGFST͕ར༻͞ΕΔ͜ͱ ͕ଟ͍ ‣ (PMBOH+BWB3VCZ/PEFKT1ZUIPO$0CK$%BSU ‣ .JDSPTFSWJDFTؒͷϓϩτίϧʹ͸͏͚ͬͯͭ

Slide 30

Slide 30 text

H31$ͱ4QSJOH ‣ HSQDTQSJOHCPPUTUBSUFSͰ4QSJOH#PPUͱ࿈ܞ ‣ IUUQTHJUIVCDPN-PH/FUHSQDTQSJOHCPPUTUBSUFS ‣ 4QSJOHͷࢿ࢈Λ׆༻͠ͳ͕ΒH31$Ͱ͖Δ ‣ !(3QD4FSWJDFͷ"OOPUBUJPOΛ࢖͏

Slide 31

Slide 31 text

H31$QSPUPCVGͷ*%- service TaskService { rpc GetTaskService (TaskInbound) returns (TaskOutbound) { option (google.api.http) = { get: "/v1/task" }; } } message TaskInbound { uint32 task_id = 1; } message TaskOutbound { uint32 task_id = 1; string title = 2; } ɹ΍ΓͱΓ͢ΔNFTTBHF H31$ϝιουͷఆٛ

Slide 32

Slide 32 text

H31$4FSWFS class TaskBackendServer( private val getTaskService: GetTaskService, private val getTaskListService: GetTaskListService ) : TaskServiceGrpc.TaskServiceImplBase() { override fun getTaskService(request: TaskInbound?, responseObserver: StreamObserver?) { val taskId = GRpcInboundValidator.validTaskInbound(request) // ૹ৴͢ΔΦϒδΣΫτͷߏங val task = getTaskService(GetTaskCommand(taskId.toLong())) val msg = TaskOutbound.newBuilder().setTaskId(taskId).build() // ݁ՌΛclient΁ responseObserver?.onNext(msg) responseObserver?.onCompleted() }

Slide 33

Slide 33 text

H31$$MJFOU suspend fun getTask(taskId: Long): TaskOutbound = async(CommonPool) { try { val outbound = ShutdownLoan.using(getChannel(), { channel -> ɹɹɹɹɹɹɹɹɹ // ͕͜͜InboundϝοηʔδߏஙɺgRPCݺͼग़͠ݩ val msg = TaskInbound.newBuilder().setTaskId(taskId.toInt()).build() TaskServiceGrpc.newBlockingStub(channel).getTaskService(msg) }) Result.Success(outbound) } catch (e: Exception) { val status = Status.fromThrowable(e) Result.Failure(status with status.description) } }.await().fold({ it }, { throw it }) DPSPVUJOFͰඇಉظॲཧΛγϯϓϧʹ

Slide 34

Slide 34 text

HPPHMFQSPUPCVGXSBQQFSTͷ׆༻ ‣ QSPUPCVGW͔Β͸SFRVJSFEϑΟʔϧυ͕ແ͍ ‣ QSPUPCVGͰ0QUJPOBMͷҙຯ෇͚͕Ͱ͖ͳ͍ ‣ IUUQTHJUIVCDPNQSPUPDPMCVGGFSTQSPUPCVG ‣ HPPHMFQSPUPCVGͷNFTTBHFΛ࢖͏ ‣ HPPHMFQSPUPCVG*OU7BMVF ‣ HPPHMFQSPUPCVG4USJOH7BMVF

Slide 35

Slide 35 text

஋͕͋Δ͔ͷҙຯ෇͚ message TaskListInbound { google.protobuf.UInt32Value page = 1; } val page = when { request.hasPage() -> request.page.value else -> DEFAULT_PAGE_LIMIT }

Slide 36

Slide 36 text

4QSJOH#PPUͱ,PUMJOॴײ ‣ ެࣜͳ,PUMJOαϙʔτຊ౰ʹ͋Γ͕͍ͨ ‣ 4QSJOHͷࢿ࢈΍ΤίγεςϜʹॿ͚ΒΕΔ ‣ ·ͣ͸3PVUFS'VODUJPO͔Β ‣ H31$ݺͼग़͠͸DPSPVUJOFͰ ‣ HPPHMFQSPUPCVGXSBQQFSTͷ׆༻Λ ‣ IUUQTHJUIVCDPNTPVTIJOTQSJOHLPUMJOBQQMJDBUJPO

Slide 37

Slide 37 text

4QBSL'SBNFXPSL

Slide 38

Slide 38 text

4QBSL'SBNFXPSL ‣ +FUUZϕʔεͷ.JDSP8FC'SBNFXPSL ‣ +BWB,PUMJOαϙʔτ ‣ جຊతͳϧʔςΟϯάػೳ ‣ 8FC4PDLFUαϙʔτ ‣ ىಈ͕ߴ଎

Slide 39

Slide 39 text

ࠇ΂͜ຊͰ΋ ѻͬͯΔΑʂ

Slide 40

Slide 40 text

CVJMEHSBEMF EFQFOEFODJFT\ JNQMFNFOUBUJPODPNTQBSLKBWBTQBSLDPSF JNQMFNFOUBUJPODPNTQBSLKBWBTQBSLLPUMJOBMQIB ^ ‣ TQBSLDPSF͚ͩͰ΋,PUMJOͰ։ൃՄೳ ‣ TQBSLLPUMJO͸%4-Ͱͷ࣮૷Λαϙʔτ͢ΔʢBMQIBʣ

Slide 41

Slide 41 text

5SZ import spark.kotlin.* fun main(args: Array) { val http: Http = ignite() http.get("/hello") { "Kotlin Fest!!" } } $ curl http://localhost:4567/hello Kotlin Fest!!

Slide 42

Slide 42 text

$POUSPMMFS package io.stormcat.controller
 
 import spark.*
 
 class EchoController {
 
 val echo = Route { req, res ->
 "Hello, ${req.queryParams("name")}!"
 }
 }

Slide 43

Slide 43 text

'JMUFST package io.stormcat.filter
 
 import spark.Filter
 import spark.Request
 import spark.Response
 
 class ResponseHeaderFilter : Filter {
 
 override fun handle(request: Request, response: Response) {
 response.header("Server", "Kotlin Fest Server")
 }
 }

Slide 44

Slide 44 text

'JMUFST package io.stormcat
 
 import io.stormcat.controller.EchoController
 import io.stormcat.filter.ResponseHeaderFilter
 import spark.Spark.*
 
 fun main(args: Array) {
 
 // filters
 after(ResponseHeaderFilter())
 
 // routing
 get("/echo", EchoController().echo)
 
 }


Slide 45

Slide 45 text

%* package io.stormcat.controller
 
 import com.google.inject.Inject
 import io.stormcat.service.UserService
 import spark.*
 
 class UserController @Inject constructor(
 val userService: UserService
 ) {
 
 val getUser = Route { req, res ->
 val userId = req.params("id")?.toLong() ?: throw RuntimeException("id is required")
 userService.getUser(userId)
 }
 }

Slide 46

Slide 46 text

%*ʢ(VJDF fun main(args: Array) {
 
 val injector = Guice.createInjector(object : AbstractModule() {
 override fun configure() {
 bindConstant().annotatedWith(
 Names.named("apiDomain")).to("api.yourexample.com")
 bind(ObjectMapper::class.java)
 .toProvider(ObjectMapperProvider::class.java)
 .`in`(Singleton::class.java)
 }
 })
 
 val userController = injector.getInstance(UserController::class.java)
 
 // routing
 get("/user/:id", userController.getUser)
 
 } ˞,PEFJOͱ͍͏%*ϥΠϒϥϦ΋͋Γ·͢ ʢ,PUMJO੡ʣ ۪௚ʹ%*͍ͯ͘͠ελΠϧ

Slide 47

Slide 47 text

ബ͍͔Β֦ுؔ਺͕େ׆༂͢Δ package io.stormcat.controller
 
 import spark.Request
 
 fun Request.authUser(): User {
 val user = this.attribute("authUser")
 return user ?: throw RuntimeException("Authorization Required")
 } XSBQQFS΍IFMQFSΑΓɺؔ਺Λੜ΍ͤʂ

Slide 48

Slide 48 text

4QBSL'SBNFXPSLॴײ ‣ ߏ੒ɺϥΠϒϥϦ૊Έ߹Θͤͷࣗ༝͞ ‣ ۪௚͞͸͋Δ͕ɺϋϚΓϙΠϯτ΋গͳ͍ ‣ ,PUMJOͰΑΓ؆ૉʹॻ͚Δ͠ɺ֦ுؔ਺౳ʹΑͬͯΑΓڧ ྗʹ΋ͳΔ

Slide 49

Slide 49 text

ӡ༻తͳ࿩ ˞,PUMJO͋·Γؔ܎ͳ͍Ͱ͕͢ɺ ӡ༻্ͷෆ҆͸ͪΌΜͱղফ͓͖ͯ͠·͠ΐ͏

Slide 50

Slide 50 text

HSQDKBWB$16௓ͶΔ໰୊ ‣ ·Εʹɺಥવ$16ར༻཰͕௓ͶΔ ‣ αʔϏεΞ΢τͯ͠΋Լ͕Γ·ͤΜ ‣ ͜͏ͳΔͱ࠶ىಈ͔͠ͳ͍ ‣ ݪҼ͸/FUUZͷ0CKFDU$MFBOFS5ISFBE ‣ IUUQTHJUIVCDPNHSQDHSQDKBWBJTTVFT

Slide 51

Slide 51 text

HSQDKBWB$16௓ͶΔ໰୊ HSQDKBWBҎ߱Λ࢖͍·͠ΐ͏

Slide 52

Slide 52 text

QSPUP؅ཧͷํ๏

Slide 53

Slide 53 text

QSPUPͲ͏؅ཧʁ ‣ .JDSPTFSWJDFTߏ੒ͰϦϙδτϦ͕ଟ͘ɺ֤ϦϙδτϦ ʹQSPUP܈͕഑ஔ͞ΕͯΔ ‣ ݺͼग़͠ݩͷ4FSWJDFଆʹɺݺͼग़͠ઌͷ4FSWJDFͷQSPUPΛ ഑ஔ ‣ QSPUPCVGHSBEMFQMVHJOͰɺQSPUP͔Βελϒࣗಈੜ੒

Slide 54

Slide 54 text

ґଘ͢ΔQSPUPϑΝΠϧूΊ

Slide 55

Slide 55 text

QSPUPEFQ ‣ IUUQTHJUIVCDPNTUPSNDBUQSPUPEFQ ‣ (JU)VC͔ΒQSPUPϑΝΠϧΛ͔͖ूΊΔπʔϧ ‣ (P੡ ‣ 50.-ϕʔεͰґଘΛఆٛ ‣ MPDLϑΝΠϧରԠ

Slide 56

Slide 56 text

&YBNQMF QSPUP@PVUEJSUIJSEQBSUZ <> UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFBQSPUP CSBODINBTUFS JHOPSFE< IPHF GVHBQSPUP > <> UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFCQSPUP SFWJTJPOW <> UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFD SFWJTJPOW QBUIQBUIQSPUPCVG

Slide 57

Slide 57 text

QBDLBHJOHEFQMPZ

Slide 58

Slide 58 text

QBDLBHJOHEFQMPZ ‣ %PDLFSͷNVUJMTUBHFCVJMEͰ%PDLFSΠϝʔδͭ͘Δ ‣ ࣮ߦίϯςφʹ͸HSBEMFXCVJMEͰͰ͖ͨTJOHMF+BS ‣ ,VCFSOFUFTσϓϩΠ༻ʹɺ)FMN$IBSU ,VCFSOFUFTͷσϓ ϩΠ༻ύοέʔδʣʹύοέʔδ͢Δ ‣ $*Ͱ͸%PDLFSΠϝʔδͱ)FMN$IBSUΛ࿉੒ ‣ IFMNJOTUBMMVQHSBEFSFQPZPVS@LPUMJO@BQQ

Slide 59

Slide 59 text

.POJUPSJOH ‣ +7..JDSPNFUFS1SPNFUIFVT ‣ ֤4FSWJDFʹ.JDSPNFUFSͷΤϯυϙΠϯτΛ࢓ࠐΈɺ 1SPNFUIFVT͔ΒεΫϨΠϐϯά ‣ 1PEʢίϯςφ܈ʣ͕࡞੒͞ΕΔͱɺࣗಈతʹ؂ࢹର৅ͱ ͳΔ

Slide 60

Slide 60 text

·ͱΊ

Slide 61

Slide 61 text

4FSWFS4JEF,PUMJOͷॴײ ‣ ͱʹ͔࣮͘༻త ‣ ͳͥօ,PUMJOͰॻ͔ͳ͍ͷ͔ ‣ +BWB,PUMJOͷ৔߹ɺࣦ͏΋ͷ͸͋·Γͳ͍Ͱ͠ΐ͏ ‣ ,PUMJO੡'SBNFXPSLʹ߆Δඞཁ͸ແ͍ ‣ ,UPS ,PEFJOFUD ‣ ٕज़બఆʹ໎ͬͨΒɺͱΓ͋͑ͣ4QSJOH#PPUબ΅͏

Slide 62

Slide 62 text

.JDSPTFSWJDFTͱͯ͠΋,PUMJO ‣ 4QBSL'SBNFXPSLɺ,UPSͷΑ͏ͳϚΠΫϩϑϨʔϜϫʔΫ ͷ׆༂ͷ৔΋ଟ͍ ‣ HSQDKBWBͰ.JDSPTFSWJDFTͷੜ࢈ੑ޲্ ‣ ʮͪΐͬͱ,PUMJOͰ.JDSPTFSWJDFTݸ࡞ΔΘʯ͕ୈҰา ‣ ҆ఆͨ͠.JDSPTFSWJDFT͕΋ͨΒ͢΋ͷ ‣ εΫϥοϓϏϧυɺ௅ઓతٕज़ͷ౤ೖͷ͠΍͢͞ ‣ Ҿ͖ଓ͖ੵۃతʹ,PUMJOΛ׆༻͍͖ͯ͠·͢

Slide 63

Slide 63 text

5IBOLT 4FSWFS4JEF,PUMJO΍͍͖ͬͯ