Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
kotlin-fest
Search
stormcat24
August 25, 2018
19k
13
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
kotlin-fest
Kotlinで愛でるMicroservices
stormcat24
August 25, 2018
More Decks by stormcat24
See All by stormcat24
素早く賢く失敗するDeveloper Productivityの実現を目指して
stormcat24
4
5.5k
KubernetesのマニフェストをそれなりにCIしたい
stormcat24
4
1.6k
令和時代のSaaS開発
stormcat24
1
360
History in 5 years of CircleCI and CyberAgent
stormcat24
3
920
Kubernetes Handson Osaka
stormcat24
5
650
Kubernetes Handson
stormcat24
5
4.6k
DockerとKubernetesでアプリケーション開発にコンテナをフル活用!
stormcat24
0
400
Base Image Journey 2018
stormcat24
30
140k
Kotlin/Native
stormcat24
0
1.1k
Featured
See All Featured
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
410
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
470
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.2k
RailsConf 2023
tenderlove
30
1.5k
Mobile First: as difficult as doing things right
swwweet
225
10k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
From π to Pie charts
rasagy
0
360
Music & Morning Musume
bryan
47
7.4k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
520
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.6k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
660
Transcript
,PUMJOͰѪͰΔ .JDSPTFSWJDFT !TUPSNDBU ,PUMJO'FTULPUMJOGFTU
XIPJT ‣ $ZCFS"HFOU *OD ‣ '3&4)-*7&5FDI-FBE ‣ IUUQTCMPHTUPSNDBUJP TUPSNDBU
%PDLFS,VCFSOFUFT ࣮ફίϯςφ։ൃೖ ຊൃച "NB[PO,JOEMF (JIZP%JHJUBM1VCMJTIJOH ָఱϒοΫεɺϤυόγυοτίϜ )POUPɺ%..DPNFUD ʢٕज़ධࣾʣ ˞ཧຊɺॳͰ"NB[POೖՙͪ
ϫΠͱ,PUMJO
None
IUUQTTQFBLFSEFDLDPNTUPSNDBUTQBSLGSBNFXPSLXJUILPUMJO
IUUQTTQFBLFSEFDLDPNTUPSNDBUUPVSPGCVJMETZTUFNJOLPUMJO
IUUQTTQFBLFSEFDLDPNTUPSNDBUOBUJWF
4QFDJBM5IBOLT ‣ IUUQCMPHTPVTIJNF ‣ ϒϩάͰαʔόαΠυ,PUMJOHSQDKBWBͷݟଟ ‣ '3&4)-*7&ͷαʔόαΠυ,PUMJOΛਪਐ ‣ ݱࡏ"OESPJEͬͯΔ ‣
ࠓՈαʔϏεʢΩϟϯϓʣ⛺Ͱෆࡏ TPVTIJO TPVTIJ@OP[BXB
͕ጪґͨͭ͠ΓͰ͠·͢ ˞ጪґϓϨθϯ
4FSWFS4JEF,PUMJOͯ͠Δͻͱ
4FSWFS4JEF,PUMJO͍͖ͬͯ ͱ͍͏ϞνϕʔγϣϯΛߴΊͯؼͬͯ΄͍͠
None
‣ ϥΠϒ৴αʔϏεʢʙʣ ‣ υϝΠϯมߋճɺαʔϏε໊มߋճ ‣ .JDSPTFSWJDFT"SDIJUFDUVSF ‣ "NB[PO&$4,VCFSOFUFTPO"84
.JDSPTFSWJDFTͷཧ༝ ‣ Մ༻ੑͷ্ͱোͷہॴԽ ‣ Ұ෦͕յΕͯɺΞϓϦέʔγϣϯશମͱͯ͠ΫϦςΟ ΧϧϛογϣϯΛߦͰ͖ΔΑ͏ʹ ‣ 4FSWJDF͝ͱʹҧ͏ݴޠɾٕज़Λར༻Ͱ͖Δ ‣ Ұ෦ͷઓతͳٕज़ͷೖ
‣ ΤϯδχΞͷϞνϕʔγϣϯʢ͜Εେ͖͍ʣ ‣ ͪΖΜେมͳ͜ͱ͋Δ
‣ (PMBOH ,PUMJOͷϋΠϒϦουߏ ‣ ৴γεςϜɺϨΠϠʔܥ(PMBOH ‣ ৽نͷ"1*جຊ,PUMJOͰ ‣ 3&45 H31$
‣ Ұ෦QVCMJDͳH31$ΤϯυϙΠϯτ͕͋Δ ݱࡏͷ'3&4)-*7&
"SDIJUFDUVSF
,PUMJO࠾༻ͷഎܠ ‣ ࠷ॳ(PҰลͩͬͨ ‣ "1*Λ(PͰ͍ͬͺ͍ॻ͍ͯ͘ͷπϥΠʢے௧ʣ ‣ .JDSPTFSWJDFTͩ͠ɺݴޠʹറΒΕͳ͍͍ͯ͘͡Όͳ͍͔
ϝϯόʔͷ৺ͷڣͼ ‣ /VMM҆શʂ ‣ ߴ֊ؔ΄͍͠ʂ ‣ ֦ுؔॻ͖͍ͨʂ ‣ EBUBDMBTT ‣
ν+BWB͔͚Δਓଟ͍ʂʢ͔͠͠+BWBͳ͍Αͳʣ ‣ +BWB͔ͩΒH31$͑Δ ‣ ,PUMJO͔Θ͍͍Α,PUMJO
(P͖Ͱ͢Αʁ ɹɹɹɹɹɹɹɹɹʢదࡐదॴେࣄʣ
։ൃํ ‣ "1*Ͱ͋Εجຊ,PUMJOͰ ‣ "1*Ͱ(Pͱੑͷߴ͍ྖҬͰ͋Ε(P ‣ ϨΠϠʔɺಈը৴ؔ࿈ɺಠࣗϛυϧΣΞͷόοΫ ΤϯυγεςϜҾ͖ଓ͖(PͰ ‣ ळ͔ΒҰ෦.JDSPTFSWJDFTͰ,PUMJOೖ։࢝
‣ ݱࡏɺ,PUMJOͷ4FSWJDF͕ݸՔಇ
࠾༻͍ͯ͠Δ'SBNFXPSL ‣ 4QSJOH#PPU ‣ 4QBSL'SBNFXPSL
4QSJOH#PPU
4UBDL ‣ 4QSJOH#PPU4QSJOH'SBNFXPSL ‣ HSQDKBWB ‣ +BDLTPO ‣ %PNB ‣
3&45ͱH31$྆ํ ‣ LPUMJOYDPSPVUJOF
3&45GVM"1* ‣ 3PVUFS'VODUJPO%4- ‣ 4QSJOHͷ,PUMJOαϙʔτͰొ ‣ ैདྷͷ.7$ͷΑ͏ʹ"OOPUBUJPOଟ༻Ͱͳ͘ɺ,PUMJOͷ%4- Ͱॻ͘
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Ͱ͞Βʹγϡοͱ͢Δ
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ͷςετ
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<TaskModel>(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" } }
H31$ ‣ 3&45GVM"1*ͷଞʹH31$ͷΤϯυϙΠϯτ༻ҙ ‣ (PPHMF͕։ൃͨ͠31$ϓϩτίϧͷҰͭ ‣ )551ϕʔε ‣ σʔλϑΥʔϚοτ1SPUPDPM#VGGFST͕ར༻͞ΕΔ͜ͱ ͕ଟ͍
‣ (PMBOH+BWB3VCZ/PEFKT1ZUIPO$0CK$%BSU ‣ .JDSPTFSWJDFTؒͷϓϩτίϧʹ͏͚ͬͯͭ
H31$ͱ4QSJOH ‣ HSQDTQSJOHCPPUTUBSUFSͰ4QSJOH#PPUͱ࿈ܞ ‣ IUUQTHJUIVCDPN-PH/FUHSQDTQSJOHCPPUTUBSUFS ‣ 4QSJOHͷࢿ࢈Λ׆༻͠ͳ͕ΒH31$Ͱ͖Δ ‣ !(3QD4FSWJDFͷ"OOPUBUJPOΛ͏
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$ϝιουͷఆٛ
H31$4FSWFS class TaskBackendServer( private val getTaskService: GetTaskService, private val getTaskListService:
GetTaskListService ) : TaskServiceGrpc.TaskServiceImplBase() { override fun getTaskService(request: TaskInbound?, responseObserver: StreamObserver<TaskOutbound>?) { val taskId = GRpcInboundValidator.validTaskInbound(request) // ૹ৴͢ΔΦϒδΣΫτͷߏங val task = getTaskService(GetTaskCommand(taskId.toLong())) val msg = TaskOutbound.newBuilder().setTaskId(taskId).build() // ݁ՌΛclient responseObserver?.onNext(msg) responseObserver?.onCompleted() }
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<TaskOutbound, GrpcException>(outbound) } catch (e: Exception) { val status = Status.fromThrowable(e) Result.Failure<TaskOutbound, GrpcException>(status with status.description) } }.await().fold({ it }, { throw it }) DPSPVUJOFͰඇಉظॲཧΛγϯϓϧʹ
HPPHMFQSPUPCVGXSBQQFSTͷ׆༻ ‣ QSPUPCVGW͔ΒSFRVJSFEϑΟʔϧυ͕ແ͍ ‣ QSPUPCVGͰ0QUJPOBMͷҙຯ͚͕Ͱ͖ͳ͍ ‣ IUUQTHJUIVCDPNQSPUPDPMCVGGFSTQSPUPCVG ‣ HPPHMFQSPUPCVGͷNFTTBHFΛ͏ ‣
HPPHMFQSPUPCVG*OU7BMVF ‣ HPPHMFQSPUPCVG4USJOH7BMVF
͕͋Δ͔ͷҙຯ͚ message TaskListInbound { google.protobuf.UInt32Value page = 1; } val
page = when { request.hasPage() -> request.page.value else -> DEFAULT_PAGE_LIMIT }
4QSJOH#PPUͱ,PUMJOॴײ ‣ ެࣜͳ,PUMJOαϙʔτຊʹ͋Γ͕͍ͨ ‣ 4QSJOHͷࢿ࢈ΤίγεςϜʹॿ͚ΒΕΔ ‣ ·ͣ3PVUFS'VODUJPO͔Β ‣ H31$ݺͼग़͠DPSPVUJOFͰ ‣
HPPHMFQSPUPCVGXSBQQFSTͷ׆༻Λ ‣ IUUQTHJUIVCDPNTPVTIJOTQSJOHLPUMJOBQQMJDBUJPO
4QBSL'SBNFXPSL
4QBSL'SBNFXPSL ‣ +FUUZϕʔεͷ.JDSP8FC'SBNFXPSL ‣ +BWB,PUMJOαϙʔτ ‣ جຊతͳϧʔςΟϯάػೳ ‣ 8FC4PDLFUαϙʔτ ‣
ىಈ͕ߴ
ࠇ͜ຊͰ ѻͬͯΔΑʂ
CVJMEHSBEMF EFQFOEFODJFT\ JNQMFNFOUBUJPODPNTQBSLKBWBTQBSLDPSF JNQMFNFOUBUJPODPNTQBSLKBWBTQBSLLPUMJOBMQIB ^ ‣ TQBSLDPSF͚ͩͰ,PUMJOͰ։ൃՄೳ ‣ TQBSLLPUMJO%4-Ͱͷ࣮Λαϙʔτ͢ΔʢBMQIBʣ
5SZ import spark.kotlin.* fun main(args: Array<String>) { val http: Http
= ignite() http.get("/hello") { "Kotlin Fest!!" } } $ curl http://localhost:4567/hello Kotlin Fest!!
$POUSPMMFS package io.stormcat.controller import spark.* class EchoController {
val echo = Route { req, res -> "Hello, ${req.queryParams("name")}!" } }
'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") } }
'JMUFST package io.stormcat import io.stormcat.controller.EchoController import io.stormcat.filter.ResponseHeaderFilter import spark.Spark.*
fun main(args: Array<String>) { // filters after(ResponseHeaderFilter()) // routing get("/echo", EchoController().echo) }
%* 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) } }
%*ʢ(VJDF fun main(args: Array<String>) { 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ʣ ۪ʹ%*͍ͯ͘͠ελΠϧ
ബ͍͔Β֦ு͕ؔେ׆༂͢Δ package io.stormcat.controller import spark.Request fun Request.authUser(): User
{ val user = this.attribute<User>("authUser") return user ?: throw RuntimeException("Authorization Required") } XSBQQFSIFMQFSΑΓɺؔΛੜͤʂ
4QBSL'SBNFXPSLॴײ ‣ ߏɺϥΠϒϥϦΈ߹Θͤͷࣗ༝͞ ‣ ۪͋͞Δ͕ɺϋϚΓϙΠϯτগͳ͍ ‣ ,PUMJOͰΑΓ؆ૉʹॻ͚Δ͠ɺ֦ுؔʹΑͬͯΑΓڧ ྗʹͳΔ
ӡ༻తͳ ˞,PUMJO͋·Γؔͳ͍Ͱ͕͢ɺ ӡ༻্ͷෆ҆ͪΌΜͱղফ͓͖ͯ͠·͠ΐ͏
HSQDKBWB$16ͶΔ ‣ ·Εʹɺಥવ$16ར༻͕ͶΔ ‣ αʔϏεΞτͯ͠Լ͕Γ·ͤΜ ‣ ͜͏ͳΔͱ࠶ىಈ͔͠ͳ͍ ‣ ݪҼ/FUUZͷ0CKFDU$MFBOFS5ISFBE ‣
IUUQTHJUIVCDPNHSQDHSQDKBWBJTTVFT
HSQDKBWB$16ͶΔ HSQDKBWBҎ߱Λ͍·͠ΐ͏
QSPUPཧͷํ๏
QSPUPͲ͏ཧʁ ‣ .JDSPTFSWJDFTߏͰϦϙδτϦ͕ଟ͘ɺ֤ϦϙδτϦ ʹQSPUP܈͕ஔ͞ΕͯΔ ‣ ݺͼग़͠ݩͷ4FSWJDFଆʹɺݺͼग़͠ઌͷ4FSWJDFͷQSPUPΛ ஔ ‣ QSPUPCVGHSBEMFQMVHJOͰɺQSPUP͔Βελϒࣗಈੜ
ґଘ͢ΔQSPUPϑΝΠϧूΊ
QSPUPEFQ ‣ IUUQTHJUIVCDPNTUPSNDBUQSPUPEFQ ‣ (JU)VC͔ΒQSPUPϑΝΠϧΛ͔͖ूΊΔπʔϧ ‣ (P ‣ 50.-ϕʔεͰґଘΛఆٛ ‣
MPDLϑΝΠϧରԠ
&YBNQMF QSPUP@PVUEJSUIJSEQBSUZ <<EFQFOEFODJFT>> UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFBQSPUP CSBODINBTUFS JHOPSFE< IPHF GVHBQSPUP
> <<EFQFOEFODJFT>> UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFCQSPUP SFWJTJPOW <<EFQFOEFODJFT>> UBSHFUHJUIVCDPNTUPSNDBUTFSWJDFD SFWJTJPOW QBUIQBUIQSPUPCVG
QBDLBHJOHEFQMPZ
QBDLBHJOHEFQMPZ ‣ %PDLFSͷNVUJMTUBHFCVJMEͰ%PDLFSΠϝʔδͭ͘Δ ‣ ࣮ߦίϯςφʹHSBEMFXCVJMEͰͰ͖ͨTJOHMF+BS ‣ ,VCFSOFUFTσϓϩΠ༻ʹɺ)FMN$IBSU ,VCFSOFUFTͷσϓ ϩΠ༻ύοέʔδʣʹύοέʔδ͢Δ ‣
$*Ͱ%PDLFSΠϝʔδͱ)FMN$IBSUΛ࿉ ‣ IFMNJOTUBMMVQHSBEFSFQPZPVS@LPUMJO@BQQ
.POJUPSJOH ‣ +7..JDSPNFUFS 1SPNFUIFVT ‣ ֤4FSWJDFʹ.JDSPNFUFSͷΤϯυϙΠϯτΛࠐΈɺ 1SPNFUIFVT͔ΒεΫϨΠϐϯά ‣ 1PEʢίϯςφ܈ʣ͕࡞͞ΕΔͱɺࣗಈతʹࢹରͱ ͳΔ
·ͱΊ
4FSWFS4JEF,PUMJOͷॴײ ‣ ͱʹ͔࣮͘༻త ‣ ͳͥօ,PUMJOͰॻ͔ͳ͍ͷ͔ ‣ +BWB,PUMJOͷ߹ɺࣦ͏ͷ͋·Γͳ͍Ͱ͠ΐ͏ ‣ ,PUMJO'SBNFXPSLʹ߆Δඞཁແ͍ ‣
,UPS ,PEFJOFUD ‣ ٕज़બఆʹ໎ͬͨΒɺͱΓ͋͑ͣ4QSJOH#PPUબ΅͏
.JDSPTFSWJDFTͱͯ͠,PUMJO ‣ 4QBSL'SBNFXPSLɺ,UPSͷΑ͏ͳϚΠΫϩϑϨʔϜϫʔΫ ͷ׆༂ͷଟ͍ ‣ HSQDKBWBͰ.JDSPTFSWJDFTͷੜ࢈ੑ্ ‣ ʮͪΐͬͱ,PUMJOͰ.JDSPTFSWJDFTݸ࡞ΔΘʯ͕ୈҰา ‣ ҆ఆͨ͠.JDSPTFSWJDFT͕ͨΒ͢ͷ
‣ εΫϥοϓϏϧυɺઓతٕज़ͷೖͷ͢͠͞ ‣ Ҿ͖ଓ͖ੵۃతʹ,PUMJOΛ׆༻͍͖ͯ͠·͢
5IBOLT 4FSWFS4JEF,PUMJO͍͖ͬͯ