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
400
Base Image Journey 2018
stormcat24
30
140k
Kotlin/Native
stormcat24
0
1.1k
Featured
See All Featured
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
180
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.8k
Visualization
eitanlees
152
17k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Product Roadmaps are Hard
iamctodd
55
12k
The Cost Of JavaScript in 2023
addyosmani
55
10k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
320
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.2k
Claude Code のすすめ
schroneko
67
230k
Paper Plane (Part 1)
katiecoart
PRO
1
9.7k
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͍͖ͬͯ