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
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.4k
KubernetesのマニフェストをそれなりにCIしたい
stormcat24
4
1.6k
令和時代のSaaS開発
stormcat24
1
350
History in 5 years of CircleCI and CyberAgent
stormcat24
3
910
Kubernetes Handson Osaka
stormcat24
5
640
Kubernetes Handson
stormcat24
5
4.5k
DockerとKubernetesでアプリケーション開発にコンテナをフル活用!
stormcat24
0
390
Base Image Journey 2018
stormcat24
30
140k
Kotlin/Native
stormcat24
0
1.1k
Featured
See All Featured
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Everyday Curiosity
cassininazir
0
230
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
0
1.7k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
So, you think you're a good person
axbom
PRO
2
2.1k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
200
BBQ
matthewcrist
89
10k
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
160
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
How to train your dragon (web standard)
notwaldorf
97
6.7k
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
330
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͍͖ͬͯ