Share
Embed
プロトコル、インターフェースとしてのGraphQL
宣言的UIの状態管理とアーキテクチャSwiftUIとGraphQLによる実践
https://speakerdeck.com/sonatard/swiftui-graphql
GraphQLの誤解
https://speakerdeck.com/sonatard/rethinking-graphql
GraphQL実践ノウハウ
https://speakerdeck.com/sonatard/graphql-knowhow
sonatard
April 25, 2023
Transcript
40 slides
-
3 ఏڙαʔϏε w "QQJGZ w 4IPQJGZͷΞϓϦΛ/P$PEFͰϦϦʔε w J04ɺ"OESPJE w 8FC্ͰΞϓϦͷσβΠϯΛมߋՄೳ
w 7*1 w 4IPQJGZʹॊೈͳձһϓϩάϥϜΛఏڙ w ϙΠϯτɺձһϥϯΫɺϦϫʔυɺ༑ୡհ w ৽αʔϏε w ։ൃத -
4 ٕज़ελοΫ w J04 w 4XJGU6* w "QPMMPJ04 w "OESPJE
w +FUQBDL$PNQPTF w "QPMMP,PUMJO w 8FC w 5ZQF4DSJQU w "QPMMP w #BDLFOE w (P w HRMHFO w ($1$MPVE3VOͳͲ -
7 (SBQI2-ͱ w جຊ w 0WFSGFUDIͷղফ w ඞཁͳ͚ͩΛऔಘ w 6OEFSGFUDIͷղফ
w ͷϦΫΤετͰඞཁͳΛͯ͢औಘ w εΩʔϚۦಈ։ൃ w εΩʔϚ͔ΒόοΫΤϯυͱΫϥΠΞϯτͷίʔυΛੜ w ୯ҰΤϯυϙΠϯτʹ2VFSZΛૹ৴͢Δ w IUUQTFYNBQMFDPNHSBQIRM -
8 2VFSZͷऔಘ type Query { viewer: User! } type User
implements Node { id: ID! order: Order! } type Order implements Node { id: ID! # defaultΛઃఆͰ͖Δ product(hasStock: Boolean = true): Product! } type Product implements Node { id: ID! name: String! } query GetProduct { viewer { order { product { name } } } } 4DIFNB 2VFSZ -
9 .VUBUJPO࡞ɺߋ৽ɺআͳͲ type Mutation { productCreate(input: ProductInput!): ProductCreatePayload! } input
ProductInput { name: String! price: String! } type ProductCreatePayload { product: Product } mutation ProductCreate($input: ProductInput!){ productCreate(input: $input) { product { id Name } } } 4DIFNB 2VFSZ -
10 4VCTDSJQUJPOετϦʔϛϯάͰΛऔಘ type Subscription { viewer: User! } type User
implements Node { id: ID! order: Order! } type Order implements Node { id: ID! product: Product! } type Product implements Node { id: ID! name: String! } subscription GetProduct { viewer { order { product { name } } } } 4DIFNB 2VFSZ -
11 εΩʔϚۦಈ։ൃ w (SBQI2-4DIFNBΛݩʹؔ࿈͢Δίʔυ͕ੜ͢ΔͷͰɺܕͷෆҰக͕ى͖ͳ͍ type Query { node(id: ID!): Node!
viewer: User! adminViewer: Admin! } -
12 (SBQI2-ͷϝϦοτ w σβΠϯͷϢʔεέʔεʹҾͬுΒΕͯɺ"1*Λมߋ͢Δඞཁ͕ͳ͍ w ΫϥΠΞϯτʮը໘9ʹ:Λදࣔ͢Δඞཁ͕Ͱ͖ͨͷͰɺ9"1*Ͱ:ΛҾ͖͍ͨʯ w 3&45ύλʔϯΫϥΠΞϯτଆ͕ଥڠ͢Δ߹ w όοΫΤϯυʮ9"1*ͷ͔Β:֎ΕΔͷͰ:"1*Λ࣮ߦͯ͠ཉ͍͠ʯ
w ΫϥΠΞϯτʮ9"1*ͱ:"1*ճ࣮ߦ͢Δ͔ʜ355Ͱ69མͪ͠ίʔυෳࡶʹͳΔͳʯ w 3&45ύλʔϯόοΫΤϯυଆ͕ଥڠ͢Δ߹ w όοΫΤϯυʮ9"1*Ͱ:ฦ͢Α͏ʹ͢Δ͔ʜͱͯ͠ҧײ͋Δ͠ɺଞͷը໘ͰΘͳ͍ͷ ʹ༨ܭͳ3FBE૿͑Δͳɻ͘͠ઐ༻ͷ9:"1*Ճ͢Δ͔ʜʯ w (SBQI2-ͷ߹ΫϥΠΞϯτόοΫΤϯυͲͪΒͤ w όοΫΤϯυʮը໘9༻ʹ9ͱ:ΛҾ͘2VFSZΛॻ͍ͯऔಘ͍͍ͯ͠Αଞͷը໘Ͱ9͚ͩΛҾ͘ 2VFSZॻ͚ຖճ:ΛҾ͘ඞཁͳ͍͔ΒύϑΥʔϚϯεͷͳ͍Αʯ w ΫϥΠΞϯτʮ355ͰࡁΉ͠ɺίʔυγϯϓϧɺը໘Ͱඞཁͳͯ͢औಘͰ͖ͯخ͍͠ʯ w όοΫΤϯυΤϯδχΞͱͯ͠ͷ (SBQI2-࠷େͷϝϦοτ -
14 6OJPO union Announcement = TextAnnouncement | URLAnnouncement | ProductAnnouncement
type TextAnnouncement { id: ID! text: String! } type URLAnnouncement { id: ID! url: String! } type ProductAnnouncement { id: ID! product: Product! } query Announcements { announcements { __typename ... on TextAnnouncement { id text } ... on URLAnnouncement { id url } ... on ProductAnnouncement { id product { id name price } } } } 4DIFNB 2VFSZ type Product { id: ID! name: String! price: Int! } -
15 const Announcements: React.FC = () => { const {
data } = useQuery(AnnouncementsDocument); return ( <div> <h2>Announcements</h2> <ul> {announcements.map((announcement) => { switch (announcement.__typename) { case "TextAnnouncement": return <li key={announcement.id}>Text: {announcement.text}</li>; case "URLAnnouncement": return ( <li key={announcement.id}> URL: <a href={announcement.url}>{announcement.url}</a> </li> ); case "ProductAnnouncement": return <li key={announcement.id}>Product: {announcement.product.name} - $ {announcement.product.price}</li>; default: return null; } })} </ul> </div> ); }; (SBQI2-ͰVOJPOͷߏΛదͳܕʹ ͕ೖͬͨঢ়ଶͰऔಘͰ͖Δ -
16 *OUFSGBDF interface Announcement { id: ID! title: String! }
type TextAnnouncement implements Announcement { id: ID! title: String! text: String! } type URLAnnouncement implements Announcement { id: ID! title: String! url: String! } type ProductAnnouncement implements Announcement { id: ID! title: String! product: Product! } query Announcements { announcements { __typename id title ... on TextAnnouncement { text } ... on URLAnnouncement { url } ... on ProductAnnouncement { product { id name price } } } } 4DIFNB 2VFSZ JEͱUJUMFJOUFSGBDFʹఆٛ͞Ε͍ͯΔͨΊ ܕʹΑΔذෆཁͰɺ ར༻͢Δ͜ͱ͕Ͱ͖Δ -
17 const Announcements: React.FC = () => { const {
data } = useQuery(AnnouncementsDocument); return ( <div> <h2>Announcements</h2> <ul> {announcements.map((announcement) => { const content = (() => { switch (announcement.__typename) { case "TextAnnouncement": return <p>Text: {announcement.text}</p>; case "URLAnnouncement": return ( <p> URL: <a href={announcement.url}>{announcement.url}</a> </p> ); case "ProductAnnouncement": return <p>Product: {announcement.product.name} - ${announcement.product.price}</p>; default: return null; } })(); return ( <li key={announcement.id}> <h3>{announcement.title}</h3> {content} </li> JEͱUJUMFJOUFSGBDFʹఆٛ͞Ε͍ͯΔͨΊ ܕʹΑΔذෆཁͰɺ ར༻͢Δ͜ͱ͕Ͱ͖Δ -
19 2VFSZ!EFGFSEJSFDUJWF query { person(id: "cGVvcGxlOjE=") { ...HomeWorldFragment @defer(label: "homeWorldDefer")
name } } fragment HomeWorldFragment on Person { homeworld { name } } w !EFGFSEJSFDUJWF༩ͨ͠2VFSZͷ༏ઌΛԼ͛Δ w (SBQI2-ɺෳͷΛಉ࣌ʹऔಘͰ͖Δ͕ɺͯ͢ͷ݁Ռ͕ἧΘͳ͍ͱϨεϙϯεΛ ฦ͞ͳ͍ɻ!EFGFSEJSFDUJWFΛઃఆ͢Δ͜ͱͰɺ४උ͕Ͱ͖ͨ'JFMEΛઌʹฦ͢͜ͱͰԠ ੑ্͕͢Δɻ -
20 2VFSZ!TUSFBNEJSFDUJWF query { person(id: "cGVvcGxlOjE=") { name films @stream(initialCount:
2, label: "filmsStream") { title } } } w !TUSFBNEJSFDUJWF༩ͨ͠2VFSZͷ݁ՌΛετϦʔϜͰड͚औΔ w -JTUͷϨεϙϯεΛऔಘ͢Δࡍʹେ͖ͳ-JTUͷ߹ʹ!TUSFBNEJSFDUJWFΛࢦఆ͢Δͱ JJUJBM$PVOUͰઃఆͨ͠ͷϨεϙϯεΛઌʹड͚औΔ͜ͱͰԠੑΛ্͛Δɻ -
22 (SBQI2-4FSWFS4QFDJ fi DBUJPO/PEFJOUFSGBDF type Query { node(id: ID!): Node!
} interface Node { id: ID! @goField(forceResolver: true) } query GetProduct($productID: ID!) { node(id: $productID) { ... on Product { id name } } } w *%Λ࣋ͭΦϒδΣΫτ/PEFJOUFSGBDFΛ࣮͢Δɻ w ͜ΕΛ࣮͢ΔͨΊʹɺ*%͔ΒͲͷΦϒδΣΫτ͔ΛผͰ͖Δඞཁ͕͋Γ·͢ɻ w (MPCBM0CKFDU*EFOUJ fi DBUJPO w 4IPQJGZͳͲҎԼͷΑ͏ͳ*%ܗࣜΛ࠾༻͍ͯ͠·͢ɻHJETIPQJGZ1SPEVDU -
23 (SBQI2-$VSTPS$POOFDUJPOT4QFDJ fi DBUJPO w $POOFDUJPO w -JTUܗࣜͷΛϖʔδωʔγϣϯͰऔಘ͢Δඞཁ͕͋Δ߹ͷ4DIFNBఆٛͷఏҊ w 999$POOFDUJPOUZQFΛఆٛ
w FEHFTPSOPEFTϑΟʔϧυΛ࣋ͭ w QBHF*OGPΛ࣋ͭ w FEHFTͷ߹ɺ999&EHFΛఆٛ w /PEFϑΟʔϧυΛ࣋ͭ w 1BHF*OGPUZQFΛఆٛ w Χʔιϧͷ։࢝ͱऴྃͷใ w લޙͷϖʔδωʔγϣϯͷଘࡏͷ༗ແ type UserConnection { pageInfo: PageInfo! edges: [UserEdge!]! nodes: [User!] } type UserEdge { node: User! } type PageInfo { startCursor: String! endCursor: String! hasNextPage: Boolean! hasPreviousPage: Boolean! } -
25 2VFSZɺ.VUBUJPOͷϑΥʔϚοτ w 3FRVFTU w 1045ͷ#PEZ { "query": "...", "operationName":
"...", "variables": { "myVariable": "someValue", ... } } { "data": { ... }, "errors": [ ... ] } w 3FTQPOTF w #PEZ -
26 2VFSZɺ.VUBUJPOͷϑΥʔϚοτ w 3FRVFTU w (&5ͷRVFSZQBSBNFUFS http://myapi/graphql?query={me{name}} w 1FSTJTUFE2VFSJFT w
2VFSZΛ4)"ͰϋογϡԽͨ͠ͷΛૹ৴ w ϦΫΤεταΠζΛখ͘͞Ͱ͖Δ w (&5ͱར༻͢Δ͜ͱͰ$%/ͰΩϟογϡՄೳʹͳΔ w ొ͞Ε͍ͯΔϋογϡͷΈΛ࣮ߦՄೳʹ͢Δ͜ͱͰ͖Δ -
27 4FSJBMJ[BUJPO'PSNBU w (SBQI2-ͷ4FSJBMJ[BUJPO'PSNBU+40/ʹݶఆ͞Ε͍ͯͳ͍ w ݱ࣮తͳੈͷதͷ࣮ͱͯ͠େ͕+40/ w .645 w .BQɺ-JTUɺ4USJOHɺ/VMM
w 4)06-% w #PPMFBOɺ*OUɺ'MPBUɺ&OVN w ରԠ͍ͯ͠ͳ͍߹4USJOHͱͯ͠ૹΒΕΔ (SBQI2-7BMVF +40/7BMVF .BQ 0CKFDU -JTU "SSBZ /VMM OVMM 4USJOH 4USJOH #PPMFBO USVFPSGBMTF *OU /VNCFS 'MPBU /VNCFS &OVN7BMVF 4USJOH -
29 4DIFNBͷཧ w (SBQI2-2VFSZͷิίʔυੜʹ4DIFNB͕ඞཁ w 4DIFNBͷใΛ֤छπʔϧ͕ΤϯυϙΠϯτ͔Β*OUSPTQFDUJPO2VFSZʹΑΓ औಘ w 4DIFNBϑΝΠϧΛΤϯυϙΠϯτ͔Β*OUSPTQFDUJPO2VFSZʹΑΓμϯϩʔυ͠ ͯɺμϯϩʔυͨ͠ϑΝΠϧΛ֤छπʔϧ͕ಡΈࠐΉ
w 4DIFNBϑΝΠϧΛϦϙδτϦ͔Βμϯϩʔυͯ͠ɺμϯϩʔυͨ͠ϑΝΠϧ Λ֤छπʔϧ͕ಡΈࠐΉ w *OUSPTQFDUJPO2VFSZΛར༻ͨ͠εΩʔϚμϯϩʔυπʔϧ w IUUQTHJUIVCDPNHRMHPHFUHSBQIRMTDIFNB -
31 GBDUPSBQQ w &WFOU4PVSDJOHɺ$234ɺ6OJEJSFDUJPOBM%BUB fl PXʹӨڹΛड͚͍ͯΔ w .VUBUJPOΠϕϯτΛهͯ͠ଈ࠲ʹϨεϙϯεΛฦͨ͢ΊԠੑ͕ߴ͍ w .VUBUJPOͷ݁ՌΛͪड͚ͯɺར༻͢ΔͷͰͳ͘.VUBUJPOʹΑΔมߋ4VCTDSJQUJPO
Λհͯ͠औಘ͢Δ୯ํσʔλϑϩʔ w .VUBUJPOΠϕϯτͷอଘ৽͍͠ঢ়ଶͷ࡞4VCTDSJQUJPOͰ௨ -
32 'FEFSBUJPO "QPMMP'FEFSBUJPO ɺ4DIFNB4UJUDIJOH w ෳͷ(SBQI2-"1*ΛΈ߹ΘͤͨεʔύʔάϥϑΛ࡞͢ΔͨΊͷΞʔΩςΫνϟ w େ͖ͳձࣾͰɺ(SBQI2-(BUFXBZͱͯ͠ѻΘΕͯɺഎޙͷ.JDSPTFSWJDFTͷϦΫ ΤετΛཧ͢Δɻ w
.JDSPTFSWJDFTνʔϜ୯ҐͰׂ͞Ε͍ͯΔ͕ɺ(SBQI2-ͷ(BUFXBZͭͳͷͰଟ͘ ͷνʔϜ͕มߋ͢ΔͨΊσϓϩΠͷͰಠཱੑͳͲ͕͘͠ͳΔɻ w ͦ͜Ͱ'FEFSBUJPOΛ͏͜ͱͰɺ(SBQI2-Λׂ͢Δɻ -
35 1SPUPDPM#V ff FSTPO)551ͱͷൺֱ w εΩʔϚͷදݱ w (SBQIߏΛදݱͰ͖Δ w 6OJPOɺ*OUFSGBDF͕ඪ४Ͱ࣋ͭͨΊදݱ͍͢͠
w SFTPMWFSʹΑΓϑΟʔϧυͷՃ͕͍͢͠ w طଘͷ2VFSZऔಘ͍ͯ͠ͳ͍ͷͰӨڹ͠ͳ͍ w ϑΟʔϧυͷআͷ͢͠͞มΘΒͳ͍ w εΩʔϚཧ w *OUSPTQFDUJPO2VFSZʹΑΓΤϯυϙΠϯτ͔ΒμϯϩʔυPSͷࢀর͕Ͱ͖Δ w ੩తղੳπʔϧͷ࣮ͷ͢͠͞ w ࣮͍ͨ͠ݴޠͷ"451BSTFS͕͋Εࠩͳ͍ -
36 1SPUPDPM#V ff FSTPO)551ͱͷൺֱ w ಠ࣮ࣗͷՃ w (SBQI2-EJSFDUJWF w 1SPUPDPM#V
ff FST1MVHJO w (SBQI2-ͷEJSFDUJWFͷํ͕ؾܰʹՃͰ͖Δ w .JDSPTFSWJDFT w .JDSPTFSWJDFTؒΛ(SBQI2-Ͱ࣮͢ΔͱεΩʔϚઃܭʹۤ࿑͢Δ w .JDSPTFSWJDFTH31$ͷࢿ࢈͕ଟ͍ w (SBQI2-(BUFXBZʹݶఆͨ͠ํ͕ߟ͑ํγϯϓϧ w 'FEFSBUJPO(BUFXBZͷׂʹ׆༻͢Δ -
37 1SPUPDPM#V ff FSTPO)551ͱͷൺֱ w ϓϩτίϧͷґଘ w (SBQI2-ྑ͘ѱ͘ଟ͘ͷϓϥΫςΟε͕༷ϑϨʔϜϫʔΫʹଘࡏ͢ΔͨΊґ ଘ͕ڧ͍ w
1SPUPDPM#V ff FSTPO)551γϯϓϧͰ͋ΓɺଞͷྨࣅϓϩίτϧͷҠߦ༰қ w ΫϥυαʔϏεͷରԠ w 8FCαʔϏε͕3&45+40/Λத৺ʹൃల͖ͯͨ͠ͷͰɺ(SBQI2-ͱϚον͠ͳ͍͜ͱ ͕ଟʑ͋Δ w ྫ ϦΫΤετͷղੳΛΤϯυϙΠϯτ͝ͱʹ͍ͬͯΔͱɺͯ͢ͷϦΫΤετΛ HSBQIRMΤϯυϙΠϯτʹ͛Δ(SBQI2-Ͱੳ͕͍͠ w (SBQI2-ઐ༻αʔϏεͷར༻ɺطଘαʔϏεͷ্Ͱ(SBQI2-Λద༻ͤ͞ΔͨΊͷ ͕ඞཁʹͳΔ w ΞϓϦ w "QPMMPJ04ɺ"QPMMP,PUMJO͕ൃల్্ w (SBQI2-1SPUPDPM#V ff FSTΑΓϥΠϒϥϦͷӨڹ͕େ͖͍ -
40 ެࣜɾඇެࣜͷཧ w (SBQI2-4QFD w 2VFSZɺ.VUBUJPOɺ4VCTDSJQUJPO w (SBQI2-4QFD8PSLJOH%SBGU w !EFGFSɺ!TUSFBN
w 3FMBZ w /PEF w $POOFDUJPO w "QPMMP w 'FEFSBUJPO w 5IF(6*-% w 4DIFNB4UJUIJOH w )BTVSB w GBDUPS"QQ w