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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
koga yushi
June 06, 2025
Technology
350
0
Share
Spring for GraphQLって実際どうなの?〜小規模スタートアップの事例紹介〜
JJUG CCC Spring 2025登壇資料です。
koga yushi
June 06, 2025
Other Decks in Technology
See All in Technology
巨大プラットフォームを進化させる「第3のROI」
recruitengineers
PRO
2
1.2k
Expiration of Secure Boot Certificates for vSphere Virtual Machines
mirie_sd
0
110
The Journey of Box Building
tagomoris
4
3.5k
UIライブラリに依存しすぎないReact Native設計を目指して
grandbig
0
140
はじめての MagicPod生成AI機能 機能紹介から活用方法まで
magicpod
0
120
20260428_Product Management Summit_Loglass_JoeHirose
loglassjoe
3
3.8k
データを"持てない"環境でのアノテーション基盤設計
sansantech
PRO
1
140
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
10
79k
今年注目する!データ分析プラットフォームでのAIの活用
nayuts
0
160
260422_Sansan_Tech_Talk__関西_vol.3_データ活用のリアル__矢田__.pdf
sansantech
PRO
0
120
ネットワーク運用を楽にするAWS DevOps Agent活用法!! / 20260421 Masaki Okuda
shift_evolve
PRO
2
230
色を視る
yuzneri
0
110
Featured
See All Featured
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
370
Deep Space Network (abreviated)
tonyrice
0
120
The Language of Interfaces
destraynor
162
26k
Marketing to machines
jonoalderson
1
5.2k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
320
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
220
Building AI with AI
inesmontani
PRO
1
910
Faster Mobile Websites
deanohume
310
31k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.9k
Docker and Python
trallard
47
3.8k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
3.8k
A Soul's Torment
seathinner
6
2.7k
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