$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Spring for GraphQLって実際どうなの?〜小規模スタートアップの事例紹介〜
Search
koga yushi
June 06, 2025
Technology
0
280
Spring for GraphQLって実際どうなの?〜小規模スタートアップの事例紹介〜
JJUG CCC Spring 2025登壇資料です。
koga yushi
June 06, 2025
Tweet
Share
Other Decks in Technology
See All in Technology
[CMU-DB-2025FALL] Apache Fluss - A Streaming Storage for Real-Time Lakehouse
jark
0
100
計算機科学をRubyと歩む 〜DFA型正規表現エンジンをつくる~
ydah
3
140
Uncertainty in the LLM era - Science, more than scale
gaelvaroquaux
0
770
AI時代の開発フローとともに気を付けたいこと
kkamegawa
0
1.4k
re:Invent 2025 ふりかえり 生成AI版
takaakikakei
1
160
20251209_WAKECareer_生成AIを活用した設計・開発プロセス
syobochim
4
1.2k
大企業でもできる!ボトムアップで拡大させるプラットフォームの作り方
findy_eventslides
0
180
グレートファイアウォールを自宅に建てよう
ctes091x
0
140
SSO方式とJumpアカウント方式の比較と設計方針
yuobayashi
5
260
Kubernetes Multi-tenancy: Principles and Practices for Large Scale Internal Platforms
hhiroshell
0
110
安いGPUレンタルサービスについて
aratako
2
2.6k
エンジニアとPMのドメイン知識の溝をなくす、 AIネイティブな開発プロセス
applism118
4
560
Featured
See All Featured
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.2k
How STYLIGHT went responsive
nonsquared
100
5.9k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
A designer walks into a library…
pauljervisheath
210
24k
Writing Fast Ruby
sferik
630
62k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
22k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How to Ace a Technical Interview
jacobian
280
24k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
1k
Typedesign – Prime Four
hannesfritz
42
2.9k
Bash Introduction
62gerente
615
210k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Transcript
Spring for GraphQL࣮ͬͯࡍͲ͏ͳͷʁ ʙখنελʔτΞοϓͰͷࣄྫհʙ Crewwגࣜձࣾ ݹլ ༐ࢤ 2025/06/07 JJUG CCC
2025 Spring
ࣗݾհ ॴଐɿCrewwגࣜձࣾ ໊લɿݹլ ༐ࢤ 𝕏 ɿ@yushi_koga Githubɿkogayushi ओઓαʔόαΠυKotlin ΠϯϑϥʢAWSʣ୲
ࠓ͢͜ͱ 1.REST APIʹฐ͕ࣾײ͍ͯͨ͡՝ 2.GraphQLͰͦΕΛͲ͏ղܾͰ͖Δͷ͔ 3.Spring for GraphQLͷ࣮ʹ͍ͭͯ • QueryΛྫʹίϯτϩʔϥʔͷ࣮ํ๏ͷհ •
Subscriptionͷ࣮Ͱ͍͠ͱ͜Ζ
REST APIʹฐ͕ࣾײ͍ͯͨ͡՝
ΫϥΠΞϯταΠυ͔ αʔόʔαΠυ͔ ͲͪΒ͔ʹ࣮ͷෛ୲͕ภΔ REST APIʹฐ͕ࣾײ͍ͯͨ͡՝
ͲΜͳ࡞ΓํΛͯͯ͠՝Λײͨ͡ͷ͔ʁ
େผ͢Δͱ͜ͷ2ͭ 1.Ϧιʔεࢦ 2.Ϣʔεέʔεࢦ ͲΜͳ࡞Γํʁ REST APIΛ࡞Δͱ͖ͷઓུ
ϦιʔεࢦͳREST APIͱʁ ͦͷ՝ʁ
ରϦιʔεࣗͷଐੑͷΈΛฦ͢ɻ ؔ࿈Ϧιʔεฦ͞ͳ͍ɻ ฦͨ͠ͱͯ͠IDͳͲඞཁ࠷খݶɻ ϦιʔεࢦͳREST APIͱʁ
Article ├ id ├ title ├ content └ authorUserId User
├ id └ name ϦιʔεࢦͳREST APIͱʁ GET /articles/{articleId} GET /users/{userId} ˢผ్ɺऔಘ͢Δ ྫ͑ɺهࣄͱͦͷߘऀ͕औΓ͍ͨͱ͖ ˢ"SUJDMFͷଐੑͷΈฦ͢ ɹ"VUIPS 6TFS ؚΊͳ͍
ϦιʔεࢦͳREST APIͷ՝ ΫϥΠΞϯτ͕REST APIΛݺͼग़͢ճ͕૿͑ɺ ΫϥΠΞϯτଆͷ࣮͕૿͑Δ
ϢʔεέʔεࢦͳREST APIͱʁ ͦͷ՝ʁ
ରϦιʔεͱؔ࿈͕͋ΔϦιʔε Ϣʔεέʔε্ඞཁͳΒҰॹʹฦ͢ ϢʔεέʔεࢦͳREST APIͱʁ
Article ├ id ├ title ├ content └ User ├
id └ name GET /articles/{articleId} ϢʔεέʔεࢦͳREST APIͱʁ ˡରϦιʔεͱؔ࿈ͷ͋ΔϦιʔεฦ͢ ྫ͑ɺهࣄͱͦͷߘऀ͕औΓ͍ͨͱ͖
ϢʔεέʔεࢦͳREST APIͷ՝ REST APIαʔόʔʹؔ࿈ϦιʔεΛ·ͱΊͯऔಘͯ͠ฦ͢ ॲཧ͕ඞཁʹͳΓɺαʔόʔαΠυͷ࣮͕૿͑Δ
Ͳ͏ͬͯղܾ͢Δ͔ʁ
Ͳ͏ͬͯղܾ͢Δ͔ ϦιʔεࢦͰ࡞ͬͨͱ͖ͱ ϢʔεέʔεࢦͰ࡞ͬͨͱ͖ͷ ྑ͍ͱ͜औΓ͕Ͱ͖ͨΒ͍͍ͷͰʁ
ͨ·ͨ·ग़ձͬͨSpring for GraphQL ͳΜͯݴޠԽͰ͖ͯͳ͔͚ͬͨͲɺ Spring for GraphQL͕GAʹͳͬͨͷͰɺ খنҊ݅Ͱࢼͯ͠Έͨ
՝ΛղܾͰ͖Δ͔ʂ Spring for GraphQL ϦιʔεࢦͰ࡞ͬͨͱ͖ͱ ϢʔεέʔεࢦͰ࡞ͬͨͱ͖ͷ ྑ͍ͱ͜औΓ͕Ͱ͖ͦ͏ͬͯࢥͬͨ
GraphQLͱԿ͔ ͲͷΑ͏ʹͯ͠՝Λղܾ͢Δͷ͔
GraphQLͱԿ͔ʁ
GraphQLͱԿ͔ʁ GraphQLAPIͷͨΊͷΫΤϦݴޠ ΫΤϦΛ࣮ߦͯ͠σʔλऔಘ͢ΔͨΊͷϥϯλΠϜ
GraphQLͱԿ͔ʁ ΫΤϦݴޠͷଆ໘ͱ ϥϯλΠϜͷଆ໘Λ ͚ͯߟ͑Δ
APIͷͨΊͷΫΤϦݴޠʁ ʢԿ͕ݴ͍͍ͨͷ͔Α͘Θ͔Βͳ͍🤔❓ʣ
ΫΤϦݴޠʹ༷ͱಡΈସ͑Α͏
GraphQLͷ༷ͱʁ GraphQLͲͷΑ͏ͳάϥϑߏ͕ଘࡏ͢Δ͔ͱɺ ͦͷάϥϑʹͲͷΑ͏ͳૢ࡞͕ ڐՄ͞Ε͍ͯΔ͔Λఆٛ͢ΔͨΊͷ༷ ͦͷ༷ͷදݱखஈ͕εΩʔϚఆٛ
͡Ό͋ɺϥϯλΠϜʁ
GraphQLͷϥϯλΠϜͱʁ ࣮ߦڥʹSpring for GraphQL
GraphQLͱSpring for GraphQLͱ ࢲୡ͕࣮͢ΔՕॴͷؔੑΛՄࢹԽͯ͠ΈΔ
GraphQLͱSpring for GraphQLͱ ࢲୡ͕࣮͢ΔՕॴͷؔੑΛՄࢹԽͯ͠ΈΔ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ GraphQLͱSpring for GraphQLͱ ࢲୡ͕࣮͢ΔՕॴͷؔੑΛՄࢹԽͯ͠ΈΔ
GraphQLREST APIͷ՝Λ Ͳ͏ղܾͰ͖Δͷ͔
REST APIʹฐ͕ࣾײ͍ͯͨ͡՝ Ϧιʔεࢦ ΫϥΠΞϯτ͕REST APIΛݺͼग़͢ճ͕૿͑ɺ ΫϥΠΞϯτଆͷ࣮͕૿͑Δ Ϣʔεέʔεࢦ REST APIαʔόʔʹؔ࿈ϦιʔεΛ·ͱΊͯऔಘͯ͠ฦ͢ ॲཧ͕ඞཁʹͳΓɺαʔόʔαΠυͷ࣮͕૿͑Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱͦͷߘ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱͦͷߘ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱͦͷߘ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱͦͷߘ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱίϝϯτͱͦͷߘऀ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱίϝϯτͱͦͷߘऀ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱίϝϯτͱͦͷߘऀ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱίϝϯτͱͦͷߘऀ໊Λऔಘ͢Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ՝ΛͲ͏ղܾͰ͖Δͷ͔ هࣄͷλΠτϧͱίϝϯτͱͦͷߘऀ໊Λऔಘ͢Δ
query articlesWithComments { articles { title comments { content author
{ name } } } } 1.৽ணهࣄʢλΠτϧʣͱߘͨ͠Ϣʔβʔ໊͕΄͍͠ 2.هࣄͷλΠτϧҰཡͱɺ͍ͭͨίϝϯτͱͦͷߘऀΓ͍ͨ ϢʔεέʔεʹԠͨ͡ΫΤϦΛॻ͚ͩ͘ query fetchNewBlogForTopPage { articles { title author { name } } }
query articlesWithComments { articles { title comments { content author
{ name } } } } 1.৽ணهࣄʢλΠτϧʣͱߘͨ͠Ϣʔβʔ໊͕΄͍͠ 2.هࣄͷλΠτϧҰཡͱɺ͍ͭͨίϝϯτͱͦͷߘऀΓ͍ͨ ϢʔεέʔεʹԠͨ͡ΫΤϦΛॻ͚ͩ͘ query fetchNewBlogForTopPage { articles { title author { name } } } ˡهࣄʢλΠτϧʣͷҰཡ
query articlesWithComments { articles { title comments { content author
{ name } } } } 1.৽ணهࣄʢλΠτϧʣͱߘͨ͠Ϣʔβʔ໊͕΄͍͠ 2.هࣄͷλΠτϧҰཡͱɺ͍ͭͨίϝϯτͱͦͷߘऀΓ͍ͨ ϢʔεέʔεʹԠͨ͡ΫΤϦΛॻ͚ͩ͘ query fetchNewBlogForTopPage { articles { title author { name } } } ˡهࣄʢλΠτϧʣͷҰཡ ˡߘͨ͠Ϣʔβʔ໊
query articlesWithComments { articles { title comments { content author
{ name } } } } 1.৽ணهࣄʢλΠτϧʣͱߘͨ͠Ϣʔβʔ໊͕΄͍͠ 2.هࣄͷλΠτϧҰཡͱɺ͍ͭͨίϝϯτͱͦͷߘऀΓ͍ͨ ϢʔεέʔεʹԠͨ͡ΫΤϦΛॻ͚ͩ͘ query fetchNewBlogForTopPage { articles { title author { name } } } ˡهࣄͷλΠτϧͷҰཡ
query articlesWithComments { articles { title comments { content author
{ name } } } } 1.৽ணهࣄʢλΠτϧʣͱߘͨ͠Ϣʔβʔ໊͕΄͍͠ 2.هࣄͷλΠτϧҰཡͱɺ͍ͭͨίϝϯτͱͦͷߘऀΓ͍ͨ ϢʔεέʔεʹԠͨ͡ΫΤϦΛॻ͚ͩ͘ query fetchNewBlogForTopPage { articles { title author { name } } } ˡهࣄͷλΠτϧͷҰཡ ˡ͍ͭͨίϝϯτ
query articlesWithComments { articles { title comments { content author
{ name } } } } 1.৽ணهࣄʢλΠτϧʣͱߘͨ͠Ϣʔβʔ໊͕΄͍͠ 2.هࣄͷλΠτϧҰཡͱɺ͍ͭͨίϝϯτͱͦͷߘऀΓ͍ͨ ϢʔεέʔεʹԠͨ͡ΫΤϦΛॻ͚ͩ͘ query fetchNewBlogForTopPage { articles { title author { name } } } ˡهࣄͷλΠτϧͷҰཡ ˡ͍ͭͨίϝϯτ ˡίϝϯτͷߘऀ
Ͳ͏ղܾ͔ͨ͠ Ϧιʔεࢦ ΫϥΠΞϯτ͕REST APIΛݺͼग़͢ճ͕૿͑ɺ ΫϥΠΞϯτଆͷ࣮͕૿͑Δ Ϣʔεέʔεࢦ REST APIαʔόʔʹؔ࿈ϦιʔεΛ·ͱΊͯऔಘͯ͠ฦ͢ॲ ཧ͕ඞཁʹͳΓɺαʔόʔαΠυͷ࣮͕૿͑Δ
Ͳ͏ղܾ͔ͨ͠ Ϧιʔεࢦ ΫϥΠΞϯτ͕REST APIΛݺͼग़͢ճ͕૿͑ɺ ΫϥΠΞϯτଆͷ࣮͕૿͑Δ ɹˠΫΤϦΛҰճݺͼग़͚ͩ͢Ͱ͢Μͩ Ϣʔεέʔεࢦ REST APIαʔόʔʹؔ࿈ϦιʔεΛ·ͱΊͯऔಘͯ͠ฦ͢ॲ ཧ͕ඞཁʹͳΓɺαʔόʔαΠυͷ࣮͕૿͑Δ
ίϯτϩʔϥʔ ίϯτϩʔϥʔ ίϯτϩʔϥʔ ΤϯςΟςΟ ΤϯςΟςΟ ΤϯςΟςΟ ͍ઢͱਤͷͱ͜ΖΛҰ࣮͢Ε ϢʔεέʔεͷόϦΤʔγϣϯʢΫΤϦʣʹԠͨ͡ ݸผͷ࣮͕͍Βͳ͍
Ͳ͏ղܾ͔ͨ͠ Ϧιʔεࢦ ΫϥΠΞϯτ͕REST APIΛݺͼग़͢ճ͕૿͑ɺ ΫϥΠΞϯτଆͷ࣮͕૿͑Δ ɹˠΫΤϦΛҰճݺͼग़͚ͩ͢Ͱ͢Μͩ Ϣʔεέʔεࢦ REST APIαʔόʔʹؔ࿈ϦιʔεΛ·ͱΊͯऔಘͯ͠ฦ͢ॲ ཧ͕ඞཁʹͳΓɺαʔόʔαΠυͷ࣮͕૿͑Δ
→ΫΤϦΛΤοδΛ࣮͢ΕɺͦΕҎ্ͷෆཁ
Spring for GraphQLͷ࣮ํ๏
εΩʔϚͷॻ͖ํ UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU
"SUJDMF ^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˡJOQVUೖྗ εΩʔϚͷॻ͖ํ
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˡJOQVUೖྗ ˡzzඞਢ εΩʔϚͷॻ͖ํ
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˡUZQFग़ྗ εΩʔϚͷॻ͖ํ
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˡUZQFग़ྗ ˢz<>zྻ εΩʔϚͷॻ͖ํ
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˡUZQFग़ྗ ˢz<>zྻ ˡzz/PU/VMM εΩʔϚͷॻ͖ํ
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˣUZQF2VFSZͱͯ͠ఆٛ εΩʔϚͷॻ͖ํ
UZQF2VFSZ\ BSUJDMFT<"SUJDMF> BSUJDMF BSUJDMF*E*% "SUJDMF ^ UZQF.VUBUJPO\ QPTU"SUJDMF JOQVU"SUJDMF*OQVU "SUJDMF
^ UZQF4VCTDSJQUJPO\ VQEBUFE"SUJDMFT BSUJDMF*ET<*%> "SUJDMF ^ JOQVU"SUJDMF*OQVU\ DPOUFOU4USJOH UJUMF4USJOH ^ UZQF"SUJDMF\ BVUIPS6TFS DPNNFOUT<$PNNFOU> DPOUFOU4USJOH JE*% MJLFE#Z<6TFS> UJUMF4USJOH ^ ˣUZQF2VFSZͱͯ͠ఆٛ ˡҾΛࢦఆՄ εΩʔϚͷॻ͖ํ
αϯϓϧίʔυͱͦͷٕज़ελοΫ ಈ͘αϯϓϧίʔυ • https://github.com/kogayushi/spring-for-graphql-tips-by-small-startup ݴޠɿ Kotlin 2.0.21 ϑϨʔϜϫʔΫɿ Spring Boot
3.4.4 • Spring for GraphQL • Spring MVC
Query࣮ྫ
type Query { article( articleId: ID! ): Article } type
Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } @Controller class ArticleGraphQLController( private val fetchArticles: FetchArticles, ) { @QueryMapping fun article( @Argument articleId : UUID ): Article? { val articles = fetchArticleByArticleId.handle(articleId) return article?.toArticleDto() } } data class Article( val id: UUID, val title: String, val content: String, val authorId: UUID, ) Query࣮ྫɿQueryͷϚοϐϯάϧʔϧ
type Query { article( articleId: ID! ): Article } type
Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } @Controller class ArticleGraphQLController( private val fetchArticles: FetchArticles, ) { @QueryMapping fun article( @Argument articleId : UUID ): Article? { val articles = fetchArticleByArticleId.handle(articleId) return article?.toArticleDto() } } data class Article( val id: UUID, val title: String, val content: String, val authorId: UUID, ) Query࣮ྫɿQueryͷϚοϐϯάϧʔϧ ˢ!2VFSZ.BQQJOH͕͍͍ͭͯΔ ಉ໊ͷϝιουʹϚοϐϯά
@Controller class ArticleGraphQLController( private val fetchArticles: FetchArticles, ) { @QueryMapping
fun article( @Argument articleId : UUID ): Article? { val articles = fetchArticleByArticleId.handle(articleId) return article?.toArticleDto() } } data class Article( val id: UUID, val title: String, val content: String, val authorId: UUID, ) type Query { article( articleId: ID! ): Article } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } Query࣮ྫɿҾͷϚοϐϯάϧʔϧ ˢ!"SHVNFOU͕͍͍ͭͯΔ ɹಉ໊ͷҾʹϚοϐϯά
@Controller class ArticleGraphQLController( private val fetchArticles: FetchArticles, ) { @QueryMapping
fun article( @Argument articleId : UUID ): Article? { val articles = fetchArticleByArticleId.handle(articleId) return article?.toArticleDto() } } data class Article( val id: UUID, val title: String, val content: String, val authorId: UUID, ) type Query { article( articleId: ID! ): Article } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } Query࣮ྫɿฦΓͷϚοϐϯάϧʔϧ ฦΓͷܕఆٛ
@Controller class ArticleGraphQLController( private val fetchArticles: FetchArticles, ) { @QueryMapping
fun article( @Argument articleId : UUID ): Article? { val articles = fetchArticleByArticleId.handle(articleId) return article?.toArticleDto() } } data class Article( val id: UUID, val title: String, val content: String, val authorId: UUID, ) type Query { article( articleId: ID! ): Article } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } Query࣮ྫɿฦΓͷϚοϐϯάϧʔϧ εΩʔϚͰఆٛͨ͠ϑΟʔϧυ໊ͱ %50ಉ໊ͷϓϩύςΟ͕Ϛοϐϯά͞ΕΔ
@Controller class ArticleGraphQLController( private val fetchArticles: FetchArticles, ) { @QueryMapping
fun article( @Argument articleId : UUID ): Article? { val articles = fetchArticleByArticleId.handle(articleId) return article?.toArticleDto() } } data class Article( val id: UUID, val title: String, val content: String, val authorId: UUID, ) type Query { article( articleId: ID! ): Article } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } Query࣮ྫɿฦΓͷϚοϐϯάϧʔϧ ˢϚοϐϯά͞Εͯͳ͍ɺ͜ͷϑΟʔϧυ&EHF
Edge࣮ྫ
Edge࣮ྫɿEdgeͷϚοϐϯάϧʔϧ @Controller class CommentGraphQLController( private val fetchComments: FetchComments, ) {
@BatchMapping(field = "comments") fun commentsOfArticle(articles: List<Article>): Map<Article, List<Comment>> { val articleIds = articles.map { it.id } val comments = fetchComments.handle( FetchCommentsInputData(articleIds) ) return articles.associateWith { val articleComments = comments .filter { comment -> comment.articleId == it.id } articleComments.map { it.toCommentDto() } } } } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } type User { id: ID! name: String! articles: [Article!]! comments: [Comment!]! }
Edge࣮ྫɿEdgeͷϚοϐϯάϧʔϧ @Controller class CommentGraphQLController( private val fetchComments: FetchComments, ) {
@BatchMapping(field = "comments") fun commentsOfArticle(articles: List<Article>): Map<Article, List<Comment>> { val articleIds = articles.map { it.id } val comments = fetchComments.handle( FetchCommentsInputData(articleIds) ) return articles.associateWith { val articleComments = comments .filter { comment -> comment.articleId == it.id } articleComments.map { it.toCommentDto() } } } } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } type User { id: ID! name: String! articles: [Article!]! comments: [Comment!]! }
Edge࣮ྫɿEdgeͷϚοϐϯάϧʔϧ @Controller class CommentGraphQLController( private val fetchComments: FetchComments, ) {
@BatchMapping(field = "comments") fun commentsOfArticle(articles: List<Article>): Map<Article, List<Comment>> { val articleIds = articles.map { it.id } val comments = fetchComments.handle( FetchCommentsInputData(articleIds) ) return articles.associateWith { val articleComments = comments .filter { comment -> comment.articleId == it.id } articleComments.map { it.toCommentDto() } } } } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } type User { id: ID! name: String! articles: [Article!]! comments: [Comment!]! } ɹɹɹɹɹɹɹɹɹˢ ΤοδͷϑΟʔϧυ໊ͱ Ξϊςʔγϣϯʹࢦఆͨ͠ϑΟʔϧυ໊ͱ Ҿͷํ͕Ұக͢ΔϝιουʹϚοϐϯά
Edge࣮ྫɿEdgeͷϚοϐϯάϧʔϧ @Controller class CommentGraphQLController( private val fetchComments: FetchComments, ) {
@BatchMapping(field = "comments") fun commentsOfArticle(articles: List<Article>): Map<Article, List<Comment>> { val articleIds = articles.map { it.id } val comments = fetchComments.handle( FetchCommentsInputData(articleIds) ) return articles.associateWith { val articleComments = comments .filter { comment -> comment.articleId == it.id } articleComments.map { it.toCommentDto() } } } } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } ϝιουͷҾʹɺؔ࿈ݩϊʔυͷ-JTU͕Θͨͬͯ͘Δˢ
Edge࣮ྫɿEdgeͷϚοϐϯάϧʔϧ @Controller class CommentGraphQLController( private val fetchComments: FetchComments, ) {
@BatchMapping(field = "comments") fun commentsOfArticle(articles: List<Article>): Map<Article, List<Comment>> { val articleIds = articles.map { it.id } val comments = fetchComments.handle( FetchCommentsInputData(articleIds) ) return articles.associateWith { val articleComments = comments .filter { comment -> comment.articleId == it.id } articleComments.map { it.toCommentDto() } } } } type Article { id: ID! title: String! content: String! author: User! comments: [Comment!]! } ίϝϯτΛऔಘͯ͠ɺ औಘϊʔυͱಥ͖߹Θͤͯˠ .BQʹͨ͠ͷΛฦ͢
Subscriptionͷ࣮Ͱ͍͠ͱ͜Ζ
Subscriptionͷ࣮Ͱ͍͠ͱ͜Ζ ReactorͷFluxΛ͏લఏʹͳ͍ͬͯΔ
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ…
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • MVCɿखଓܕʢ໋ྩܕʣϓϩάϥϛϯά •
FluxɿReactiveʢԠܕʣϓϩάϥϛϯά
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • MVCɿखଓܕʢ໋ྩܕʣϓϩάϥϛϯά •
FluxɿReactiveʢԠܕʣϓϩάϥϛϯά ύϥμΠϜ͕ҧ͏
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • MVCɿखଓܕʢ໋ྩܕʣϓϩάϥϛϯά •
FluxɿReactiveʢԠܕʣϓϩάϥϛϯά ύϥμΠϜͷؒͷհ͕ඞཁ
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • MVCɿखଓܕʢ໋ྩܕʣϓϩάϥϛϯά •
FluxɿReactiveʢԠܕʣϓϩάϥϛϯά ύϥμΠϜͷؒͷհ͕ඞཁ ReactorͷSinks
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • FluxͱSinksͷษڧ͕ඞཁ
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • FluxͱSinksͷษڧ͕ඞཁ •
Sinksͷ͍ํ͕͍͠
FluxΛ͏લఏͩͱͳ͍ͥ͠ͷ͔ʁ • Spring Web fl uxͰͳ͘Spring MVCͰಈ͔ͤ͢Δͷͷ… • FluxͱSinksͷษڧ͕ඞཁ •
Sinksͷ͍ํ͕͍͠ • ಉ࣌ʹemitΤϥʔ → εϨουηʔϑతͳ͕ඞཁ͔ʁ • BlockingQueueΛͬͯɺඇಉظॲཧΛಉظॲཧʹมߋ • ϫʔΧʔεϨουͰQueue͔ΒऔΓग़ͯ̍݅ͣͭ͠emit • ಈ͘αϯϓϧίʔυʹ࣮ྫΛ༻ҙͨ͠ • https://github.com/kogayushi/spring-for-graphql-tips-by-small-startup
՝ղܾͰ͖ͨͷ͔ʁ Ϧιʔεࢦͷ՝ ΫϥΠΞϯτ͕REST APIΛݺͼग़͢ճ͕૿͑ɺ ΫϥΠΞϯτଆͷ࣮͕૿͑Δ ɹˠ ҰճͷΫΤϦݺͼग़͠ͰඞཁͳσʔλΛऔಘɿ🆗 Ϣʔεέʔεࢦͷ՝ REST APIαʔόʔʹؔ࿈ϦιʔεΛ·ͱΊͯऔಘͯ͠ฦ͢ॲཧ
͕ඞཁʹͳΓɺαʔόʔαΠυͷ࣮͕૿͑Δ ɹˠΫΤϦͱΤοδͷ࣮͢Δ͚ͩͰࡁΉɿ🆗
ϝϦοτʁ • ϑϩϯτΤϯυɺόοΫΤϯυͱʹ։ൃޮ্͕🆙 • ϑϩϯτΤϯυσʔλಥ͖߹Θͤॲཧ͕ͳ͘ͳ࣮ͬͯྔ͕ݮͬͨ • όοΫΤϯυ࣮ର͕ݮΓɺ։ൃྔΛେ͖͘ݮΒͤͨ
σϝϦοτʁ • Subscriptionͷ࣮қ͕ߴ͍🙄 • ͜ͷ͋ͱհ͢Δɺಈ͘αϯϓϧίʔυ͕ࢀߟʹͳΔ͔ʁ • ϝϯόʔͷܦݧ͕ઙ͍͏ͪɺEdgeͷݺͼग़͠ͰN+1සൃ͕ͪ͠🐢 • BatchMappingͷར༻ΛపఈΛపఈ͢Ε͋Δఔղܾ͢Δ •
ϑΝΠϧΞοϓϩʔυํ๏ͷਖ਼ղ͕ʢࠓͷͱ͜ΖʣΘ͔Βͳ͍🤷 • GraphQLʹϑΝΠϧΞοϓϩʔυͷ༷͕ͳ͍ • ैདྷͷΓํͰΔ͔͠ͳ͍ʁࡧத • εΩʔϚఆٛͷߋ৽࣌ɺࠩΛ͑Δํ๏ʹΉ • ·͍͍ͩΓํ͕ࢥ͍͍ͭͯͳ͍ɺࡧத • ࠓslackͰڞ༗ɺਓྗରॲ💪
ϦϑΝϨϯε • ϒϩάهࣄ 1.ංେԽ͢ΔεΩʔϚఆٛͷରॲํ๏ https://qiita.com/yushi_koga/items/f05a7b23b20e61aa1269 2.෦ߋ৽͍ͨ͠ https://qiita.com/yushi_koga/items/f749dd53e32fafb23eee 3.MutationͷΓͷํΛVoidʹ͢Δํ๏ https://qiita.com/yushi_koga/items/45b87eff5b40a5ec86f0 4.ࢄτϨʔγϯάͰGraphQLͷ۠ผΛ͚ͭΔํ๏
https://qiita.com/yushi_koga/items/8051f98e093dbbe8f51d • ಈ͘αϯϓϧίʔυ https://github.com/kogayushi/spring-for-graphql-tips-by-small-startup
Spring for GraphQL࣮ͬͯࡍͲ͏ͳͷʁ ʙখنελʔτΞοϓͰͷࣄྫհʙ Crewwגࣜձࣾ ݹլ ༐ࢤ 2025/06/07 JJUG CCC
2025 Spring