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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
koga yushi
June 06, 2025
Technology
380
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Spring for GraphQLって実際どうなの?〜小規模スタートアップの事例紹介〜
JJUG CCC Spring 2025登壇資料です。
koga yushi
June 06, 2025
Other Decks in Technology
See All in Technology
ホームラボ紹介
y_sera15
0
230
AI-DLC実践録_フルサイクル開発への挑戦
miyuc
0
380
【GCC2026】TrueHDRIを用いたルックデブ環境とライティングテクニック
bandainamcostudios
PRO
0
270
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
12
85k
What's new in Go 1.27?
ciarana
0
300
FORENSIA: ローカルLLMフォレンジックハーネス
sumeshi
2
440
社内の7割が使うデータ基盤を、 データチーム2人で回すためにやったこと
koh_yoshi
4
1.5k
SmartHR Engineering Team Deck
smarthr
1
3.4k
AIに狂うスタートアップが、あえて「人との協働」に全振りした新卒エンジニア研修 / New Graduate Engineer Training at a Startup Accelerating AI Adoption
ohnoeight
0
180
Sets in Go
ramalho
1
1.2k
dbt と Snowflake と tag
kevinrobot34
1
130
トークンマネジメントでAIにとって働きやすい環境を実現する
hikaruegashira
0
140
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
540
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
620
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
220
Optimising Largest Contentful Paint
csswizardry
37
3.9k
Scaling GitHub
holman
464
140k
Claude Code のすすめ
schroneko
67
230k
Tell your own story through comics
letsgokoyo
1
1k
Darren the Foodie - Storyboard
khoart
PRO
3
3.7k
New Earth Scene 8
popppiees
3
2.5k
Chasing Engaging Ingredients in Design
codingconduct
0
280
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.1k
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