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
Spring for GraphQLって実際どうなの?〜小規模スタートアップの事例紹介〜
Search
koga yushi
June 06, 2025
Technology
0
220
Spring for GraphQLって実際どうなの?〜小規模スタートアップの事例紹介〜
JJUG CCC Spring 2025登壇資料です。
koga yushi
June 06, 2025
Tweet
Share
Other Decks in Technology
See All in Technology
Kiro Hookを Terraformで検証
ao_inoue
0
140
完璧を目指さない小さく始める信頼性向上
kakehashi
PRO
0
110
FAST導入1年間のふりかえり〜現実を直視し、さらなる進化を求めて〜 / Review of the first year of FAST implementation
wooootack
1
180
ML Pipelineの開発と運用を OpenTelemetryで繋ぐ @ OpenTelemetry Meetup 2025-07
getty708
0
320
MCPに潜むセキュリティリスクを考えてみる
milix_m
1
870
AIを使っていい感じにE2Eテストを書けるようになるまで / Trying to Write Good E2E Tests with AI
katawara
3
1.9k
CSPヘッダー導入で実現するWebサイトの多層防御:今すぐ試せる設定例と運用知見
llamakko
1
260
【CEDEC2025】現場を理解して実現!ゲーム開発を効率化するWebサービスの開発と、利用促進のための継続的な改善
cygames
PRO
0
370
ユーザー理解の爆速化とPdMの価値
kakehashi
PRO
1
110
claude codeでPrompt Engineering
iori0311
0
530
自分がLinc’wellで提供しているプロダクトを理解するためにやったこと
murabayashi
1
170
Railsの限界を超えろ!「家族アルバム みてね」の画像・動画の大規模アップロードを支えるアーキテクチャの変遷
ojima_h
4
520
Featured
See All Featured
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Embracing the Ebb and Flow
colly
86
4.8k
Measuring & Analyzing Core Web Vitals
bluesmoon
7
530
A better future with KSS
kneath
238
17k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
48
2.9k
KATA
mclloyd
30
14k
What’s in a name? Adding method to the madness
productmarketing
PRO
23
3.6k
The Language of Interfaces
destraynor
158
25k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
161
15k
Typedesign – Prime Four
hannesfritz
42
2.7k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Making the Leap to Tech Lead
cromwellryan
134
9.4k
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