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

kotlin-fest

stormcat24
August 25, 2018
16k

 kotlin-fest

Kotlinで愛でるMicroservices

stormcat24

August 25, 2018
Tweet

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. ϫΠͱ,PUMJO

    View Slide

  5. View Slide

  6. IUUQTTQFBLFSEFDLDPNTUPSNDBUTQBSLGSBNFXPSLXJUILPUMJO

    View Slide

  7. IUUQTTQFBLFSEFDLDPNTUPSNDBUUPVSPGCVJMETZTUFNJOLPUMJO

    View Slide

  8. IUUQTTQFBLFSEFDLDPNTUPSNDBUOBUJWF

    View Slide

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

    View Slide

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

    View Slide

  11. 4FSWFS4JEF,PUMJOͯ͠Δͻͱ

    View Slide

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

    View Slide

  13. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. "SDIJUFDUVSF

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. 4QSJOH#PPU

    View Slide

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

    View Slide

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

    View Slide

  26. 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Ͱ͞Βʹγϡοͱ͢Δ

    View Slide

  27. 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ͷςετ

    View Slide

  28. 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"
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  31. 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$ϝιουͷఆٛ

    View Slide

  32. 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()
    }

    View Slide

  33. 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ͰඇಉظॲཧΛγϯϓϧʹ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. 4QBSL'SBNFXPSL

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. $POUSPMMFS
    package io.stormcat.controller


    import spark.*


    class EchoController {


    val echo = Route { req, res ->

    "Hello, ${req.queryParams("name")}!"

    }

    }

    View Slide

  43. '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")

    }

    }

    View Slide

  44. '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)


    }


    View Slide

  45. %*
    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)

    }

    }

    View Slide

  46. %*ʢ(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੡ʣ
    ۪௚ʹ%*͍ͯ͘͠ελΠϧ

    View Slide

  47. ബ͍͔Β֦ுؔ਺͕େ׆༂͢Δ
    package io.stormcat.controller


    import spark.Request


    fun Request.authUser(): User {

    val user = this.attribute("authUser")

    return user ?: throw RuntimeException("Authorization Required")

    }
    XSBQQFS΍IFMQFSΑΓɺؔ਺Λੜ΍ͤʂ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. QSPUP؅ཧͷํ๏

    View Slide

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

    View Slide

  54. ґଘ͢ΔQSPUPϑΝΠϧूΊ

    View Slide

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

    View Slide

  56. &YBNQMF
    [email protected]
    <>
    UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFBQSPUP
    CSBODINBTUFS
    JHOPSFE<
    IPHF
    GVHBQSPUP
    >
    <>
    UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFCQSPUP
    SFWJTJPOW

    <>
    UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFD
    SFWJTJPOW
    QBUIQBUIQSPUPCVG

    View Slide

  57. QBDLBHJOHEFQMPZ

    View Slide

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

    View Slide

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

    View Slide

  60. ·ͱΊ

    View Slide

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

    View Slide

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

    View Slide

  63. 5IBOLT
    4FSWFS4JEF,PUMJO΍͍͖ͬͯ

    View Slide