Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Public API with GraphQL

joe_re
January 30, 2025

Building Public API with GraphQL

登壇資料: GraphQLが可能にする「ワンランク上」のAPI開発
https://rosca.connpass.com/event/342430/

Public APIをGraphQLで提供するにあたっての事例紹介と実装のポイント。

joe_re

January 30, 2025
Tweet

More Decks by joe_re

Other Decks in Technology

Transcript

  1. About me ▸ Name: Masato Noguchi ▸ X: @joe_re ▸

    GitHub: @joe-re ▸ Tech Lead at CureApp Inc. ▸ One of the organizer of GraphQL Tokyo
  2. Today’s topics ▸ 1. What is GraphQL and is it

    a good fi t for Public API? ▸ 2. Introduce our case study ▸ 3. Implementation points especially for Public APIs
  3. Describe your data ▸ ΫϥΠΞϯτଆͰඞཁͳσʔλΛબ୒ͯ͠औಘ͢Δ ▸ query GetPatientAllergies($id: ID!) {

    patient(id: $id) { allergies { substance reaction } } } { "data": { "patient": { "allergies": [ { "substance": "Peanuts", "reaction": "Anaphylaxis" } ] } } } ΞϨϧΪʔ৘ใͷΈΛฦ٫͢Δ (ΦʔόʔϑΣονϯάͷղফ) ױऀͷΞϨϧΪʔ৘ใΛ஌Γ͍ͨʂ
  4. Get many resources in a single request ▸ 1ͭͷϦΫΤετͰෳ਺ͷϦιʔεΛऔಘ͢Δ query

    GetPatientWithRecords($id: ID!) { patient(id: $id) { name medicalRecords { date diagnosis doctor { name specialty } } } } "data": { "patient": { "name": "John Doe", "medicalRecords": [ { "date": "2024-01-15", "diagnosis": "Hypertension", "doctor": { "name": "Dr. Smith", "specialty": "Cardiology" } }, { "date": "2023-12-10", "diagnosis": "Common Cold", "doctor": { "name": "Dr. Johnson", "specialty": "General Practice" } } ] } } } ωετ͍ͯ͠Δ৘ใ΋ؚΊͯҰ౓ͷϨεϙϯεͰཁٻ͞Εͨσʔ λΛฦ٫͢Δɻ(ΞϯμʔϑΣονͷղফ) ױऀͷ໊લͱ਍࡯৘ใΛ஌Γ͍ͨʂ ਍࡯৘ใʹ͸೔෇ͱ୲౰ҩͷ৘ใΛؚΊͯʂ
  5. Describe your data in a way that clients can easily

    understand ▸ ΫϥΠΞϯτ͕ཧղ͠΍͍͢ํ๏ͰσʔλΛهड़͢Δ(εΩʔϚͷఆٛ) type Patient { id: ID! name: String! age: Int gender: String contactInfo: ContactInfo medicalRecords: [MedicalRecord] } type ContactInfo { email: String phone: String } type MedicalRecord { date: String! diagnosis: String! treatmentPlan: String doctor: Doctor! } type Doctor { id: ID! name: String! specialty: String! } query GetPatientContactAndDoctor($id: ID!) { patient(id: $id) { contactInfo { email phone } medicalRecords { doctor { name specialty } } } } ఏڙ͞ΕͨεΩʔϚ৘ใ͔ΒΫΤϦΛ૊ΈཱͯΔ͜ͱ͕Ͱ͖Δ ͜ͷAPIͬͯױऀͷ࿈བྷઌͱ୲౰ҩͷઐ໳෼໺Λ औಘ͢Δʹ͸Ͳ͏͢ΔΜ͚ͩͬʁ
  6. Strong points of GraphQL ▸ one-size- fi ts-all(OSFA) APIs ▸

    1ͭͷΤϯυϙΠϯτͰશͯΛදݱ͢ΔεΩʔϚΛఏڙ͢Δ ▸ ΫϥΠΞϯτ͕ΫΤϦΛܾఆ͢ΔͷͰɺͦΕͧΕͷϢʔεέʔεʹ࠷దԽͨ͠ΫΤϦΛൃߦͰ͖Δ ▸ Schema as API Contract ▸ Schema͕ͦͷ··API ContractͱͳΓɺΫϥΠΞϯτ/αʔόؒͰͷ९क͢΂͖ϧʔϧ΍ೖग़ྗ͕ ఆٛ͞ΕΔ ▸ Schema͸ܕ৘ใ΋ఏڙ͢ΔͨΊɺΫϥΠΞϯτଆͰπʔϧΛ༻͍ͨܕ҆શੑͷ୲อͳͲ͕͠΍͢ ͍
  7. Concerned points of Graph QL (vs REST) ▸ Non-compliant with

    HTTP speci fi cations ▸ REST͸HTTP࢓༷ʹ४ڌ͢Δ͜ͱͰAPI࢓༷ʹ໌֬͞Λ࣋ͨͤΔ͜ͱ͕Ͱ͖͕ͨɺGraphQL͸HTTP࢓༷ʹ४ڌ͠ͳ͍ͷͰɺ࣮૷࣍ୈͱ ͳΔ෦෼͕͋Γɺᐆດ͕͞ൃੜ͠΍͍͢ ▸ ඪ४Խͷಈ͖΋͋Γɺ࣮ࡍʹ2025/01/01ʹ `Content-Type: application/graphql-response+json` ͕࢓༷ͱͯ͠ඪ४Խ͞Εͨɻ͜ΕʹΑ ΓGraphQL RequestͰฦ٫͢Δεςʔλείʔυ͕໌֬ʹͳͬͨɻ ʢhttps://graphql.github.io/graphql-over-http/draft/#sec-application-graphql-response-json) ▸ Security, Performance, Cashing ▸ GraphQLͷ໰୊ͱ͍͏ΑΓ΋REST࣮૷ʹ׳Εͨݱ৔Ͱͷಋೖ࣌ʹൃੜ͠ಘΔ໰୊Ͱ͸͋Δ͕ɺηΩϡϦςΟ΍ύϑΥʔϚϯεΛอͭͨ ΊͷϓϥΫςΟε͕RESTͱҟͳΔ෦෼͕ଟ͍ͨΊɺ࣮૷ʹ׳Ε͍ͯͳ͍ͱۤ࿑͢Δ͜ͱ͕͋Δ ▸ ͦΕͧΕͷΤϯυϙΠϯτͰดͨ͡ίϯςΩετΛ࣋ͭRESTʹൺ΂ɺҰ෦ͷ࣮૷͕ٴ΅͢Өڹൣғ͕େ͖͘ͳΔ ▸ Learning cost
  8. Is GraphQL a good fit for Public API? ▸ ։ൃνʔϜɺର৅ϢʔβɺΞϓϦέʔγϣϯಛੑʹΑΔ

    ▸ GraphQLͷར఺͸ΫϥΠΞϯτଆͷϢʔεέʔεΛεΩʔϚͱͯ͠දݱ͢Δ͜ͱͰൃش͞Ε Δɻෆಛఆଟ਺ͷϢʔβ͕͍ΔதͰϢʔεέʔεͷಛఆʹूத͢Δ͜ͱ͕Ͱ͖Δ͔ʁ ▸ ΦʔόʔϑΣονɺΞϯμʔϑΣονͷղফ͸WebAppͷίϯςΩετͰ͸ޮՌ͕͋ΔΑ͏ ʹײ͡Δ͕ɺPublic APIͰͦΕΛఏڙ͢Δඞཁ͕͋Δ͔ʁ (ͦΕͧΕͷΫϥΠΞϯτͷϢʔεέʔεʹدΓఴ͑ͳ͍ͱͳ͔ͳ͔ΫΤϦͷ࠷దԽ͸Ͱ͖ͳ ͍͕ɺෆಛఆଟ਺ͷΫϥΠΞϯτશͯʹدΓఴ͏ͷ͸೉͍͠ɻ) ▸ ΫϥΠΞϯτʹGraphQLͷֶशίετΛෛ୲ͤ͞ͳ͍͔ʁ
  9. Who is GraphQL suitable for? ▸ Private APIͰ͢ͰʹGraphQL࣮૷͕͋Δ ▸ ͢ͰʹPrivate

    APIͰΞϓϦέʔγϣϯͷϢʔεέʔε͕GraphQLʹදݱ͞Ε͍ͯΔ৔߹ɺPublic APIͷߏஙͰ͸ͦͷચ࿅ɺҰൠԽʹूத͢Ε͹ྑ͍෦෼͕ଟ͍ͷͰߏங͠΍͍͢ɻResolverͷ࠶ ར༻ੑΛલఏʹ࣮ͯ͠૷͢Δ͜ͱ΋͋Γɺ޻෉Λ͢Ε͹࣮૷͕ڞ༗ՄೳͰ͋Δ෦෼΋ଟ͘ͳΔɻ ▸ ఏڙ͢ΔAPIʹػೳ͕ଟ͍ ▸ OSFA APIsͰ͸୯ҰΤϯυϙΠϯτͰεΩʔϚͷશͯͷ৘ใʹΞΫηεͰ͖Δ͜ͱʹར఺͕͋ Δɻͦ΋ͦ΋ΤϯυϙΠϯτ͕͋·Γଟ͘ͳ͘ɺকདྷతʹ΋ͦ͜·Ͱ૿͑ͳ͍ͷͳΒGraphQL ΛPublic APIͱͯ͠ఏڙ͢Δར఺͸ബ͘ͳΔɻ
  10. Case study of our company ▸ ࣏ྍΞϓϦͷϕϯμʔ޲͚ʹɺҩྍࢪઃ΁ͷαϙʔτ΍ར༻ঢ়گͷ෼ੳΛߦ͏ͨ ΊͷGraphQL APIΛఏڙ͍ͯ͠Δ ▸

    APIΛ௚઀ఏڙ΋͍ͯ͠Δ͕ɺࣗલͷ؅ཧը໘Λ࣋ͨͳ͍ϕϯμʔ޲͚ʹ͸ɺ࣮ ૷ͤͣͱ΋ಉ౳ͷػೳΛ࢖༻Մೳͱ͢ΔͨΊɺWebΞϓϦέʔγϣϯͱͯ͠Ϛ ωʔδϝϯτίϯιʔϧ΋ఏڙ͍ͯ͠Δ
  11. Purpose of the implementation ▸ Ϛωʔδϝϯτίϯιʔϧɺ௚઀APIΛ࢖͏έʔεΛͦΕͧΕର৅ͷϢʔεέʔεͱͨ͠ ▸ ͭ·ΓϚωʔδϝϯτίϯιʔϧͰͷϢʔεέʔεͷऩू + ࣮૷Λߦ͏͜ͱ

    = Public APIͷ੒௕ʹߩݙͱͳΔΑ͏ʹ͠ ͨ(ͦͷཧ༝ʹ͸Ϗδωεཁ݅΋ؚΉ) ▸ GraphQL͸1ͭͷΤϯυϙΠϯτͰෳ਺ͷϢʔεέʔε(ΫϥΠΞϯτ)ͷχʔζΛຬͨ͢͜ͱΛಘҙͱ͢ΔͨΊɺϚωʔ δϝϯτίϯιʔϧͱAPIΛ௚઀࢖͏2ͭͷϢʔεέʔεΛಉ࣌ʹαϙʔτ͠΍͍͢ ▸ GraphQL APIs͸GraphQL GatewayͰෳ਺αʔϏεΛ·ͱΊͨ୯ҰΤϯυϙΠϯτΛఏڙ͢Δ ▸ ΞΫηε੍ݶɺRate/LimitingͳͲͷؔ৺͝ͱͷ෼཭ ▸ কདྷతʹҟͳΔίϯςΩετɺϢʔβɺϢʔεέʔε͕ൃੜͨ࣌͠ͷॊೈੑͷ֬อ ▸ όʔδϣϯ؅ཧͷ༰қ͞
  12. Agenda ▸ 3.1. Naming rules of schema ▸ 3.2. Rate

    limiting ▸ 3.3. Schema change management and versioning
  13. Examples of supporting use cases ▸ ୯਺ܥͱෳ਺ܗͷΫΤϦ͸྆ํఆٛ͢Δ ▸ user(୯਺)ͱusers(ෳ਺)Λऔಘ͢ΔྫΛߟ͑ΔͱɺusersͷAPI͑͋͞Ε͹userͷέʔε͸ຬͨͤΔ͕ɺΫϥΠΞϯτଆͷࢹ఺ʹཱͯ͹ user͕ཉ͍͠Ϣʔεέʔεͱusers͕ཉ͍͠Ϣʔεέʔε͸໌֬ʹҟͳΔ͜ͱ͕ଟ͍(list

    page, detail page) ▸ ͳΔ΂͘ϢʔεέʔεΛද໊͢લʹ͢Δ ▸ ྫ: github͸ɺstarΛ෇͚ΔmutationΛupdateStarͰ͸ͳ͘addStar/removeStarͰදݱ͍ͯ͠Δ ▸ Fine-Grained or Coarse-Grained ▸ Ϧιʔε୯ҐͰͷCRUDૢ࡞Λجຊͱ͢ΔRESTʹൺ΂ΔͱɺGraphQLͰ͸mutationͷཻ౓͸ࣗ෼Ͱܾఆ͢Δ͜ͱ͕Ͱ͖Δ ▸ ॻ੶Production Ready GraphQLͰ͸ɺcreate͸Coarse-GrainedͰupdate͸Fine-GrainedʹͳΔ͜ͱΛਪ঑͍ͯ͠Δ mutation { updateProduct( id: "123", input: { name: "New Product Name" price: 99.99 stock: 100 labels: ["New Arrival", "Best Seller"] } ) } mutation { addLabel(productId: "123", label: "New Arrival") }
  14. A schema that easily keeping backward compatibility ▸ Mutationͷಈࢺ(Action)ͷҐஔͱछྨΛ౷Ұ͢Δ ▸

    ಈࢺ + Object(addStar) or Object + ಈࢺ(starAdd)ʹ͢Δ͔ɻಈࢺ͸ϢʔεέʔεΛද͍ͤͯΔ͔ɻ(updateΛ࢖͍ͨ͘ͳͬͨΒҰ౓ཱ ͪࢭ·Δ΂͖ͳ͜ͱ͕ଟ͍ɻ *͋͑ͯcoarse-grainedʹ͍ͨ͠৔߹ʹ͸updateΛ࢖͏৔߹΋͋Δ) ▸ ΤϥʔΛΫϥΠΞϯτʹฦ͢खஈΛܾΊ͓ͯ͘ ▸ Τϥʔ৘ใΛUnion typesͰฦ͢ or ΤϥʔΛฦ͢ϑΟʔϧυΛMutationͷϨεϙϯεʹؚΊΔΑ͏ʹ͢Δ ▸ Union typesͰฦ͢৔߹ʹ͸ɺΤϥʔ৘ใͷ௥Ճ࣌ʹbreaking changesΛҾ͖ى͜͞ͳ͍ͨΊɺશͯͷmutationͷϨεϙϯεΛ(ͨͱ͑ ࣮૷࣌ʹΤϥʔΛฦ͢༧ఆ͕ͳ͘ͱ΋)union typesʹ͓ͯ͘͠ ▸ MutationͷΤϥʔ͸EntityͷtypeΛ௚઀ฦ͞ͳ͍(ྫ: InviteMemberPayload) ▸ Non-nullͰ஋Λฦ٫͢ΔέʔεΛݶఆ͢Δ ▸ GraphQLʹ͓͍ͯ͸non-null΁ͷมߋ͸breaking changesʹͳΔͷͰɺσϑΥϧτ͸nullableʹͳ͍ͬͯΔɻྫ͑͹user(id: ID!)͸Ϣʔ ͕ݟ͔ͭΒͳ͍৔߹ʹ͸nullΛฦ٫͢Δɻ͜ͷΑ͏ͳέʔε͕૝ఆͰ͖ΔͳΒnon-nullʹ͢Δ΂͖Ͱ͸ͳ͍ ▸ MutationͷҾ਺͸༳Ε΍͍͢ͷͰɺinput objectΛ࢖͏έʔεΛܾΊ͓ͯ͘ * ৄࡉΛઆ໌͢Δ࣌ؒ͸(͓ͦΒ͘)ͳ͍ͷͰɺৄ͘͠͸ҎԼͷهࣄΛࢀর͍ͯͩ͘͠͞🙇 GraphQLͷεΩʔϚઃܭʹ͓͍ͯॳظஈ֊͔Βߟ͓͑ͯ͘΂͖͜ͱ ~ Mutationฤ ~ https://zenn.dev/cureapp/articles/2c670b5b564f86
  15. How GraphQL rate limiting is different from REST ▸ GraphQLʹ͓͍ͯ͸ɺ1ͭͷϦΫΤετͰෳ਺ͷREST

    APIͷݺͼग़͠ʹ૬౰͢Δ ෛՙΛαʔόʹ͔͚ΔՄೳੑ͕͋ΔͨΊɺ୯७ʹϦΫΤετ਺ͷΧ΢ϯτΛ΋ͱ ʹͯ͠rate limitingΛ͔͚Δ͜ͱ͸Ͱ͖ͳ͍ ▸ ࣮ࡍʹͲΕ͙Β͍ͷෛՙ͕͔͔Δ͔͸ɺGraphQLΛ࣮ߦ͢Δ·Ͱܾఆ͢Δ͜ͱ͸ Ͱ͖ͳ͍ ▸ ಛʹෆಛఆͷΫϥΠΞϯτ͔Β࣮ߦ͞ΕΔՄೳੑͷ͋ΔPublic APIʹ͓͍ͯ͸ɺ ద੾ͳrate limitingΛ͔͚͓ͯ͘͜ͱ͕ॏཁʹͳΔ
  16. Static analysis is important for blocking queries before they are

    executed ▸ Rate limitingΛ͔͚Δ࣌ʹɺGraphQLͰ͸ϦΫΤετ਺ΛΧ΢ϯτ͢ΔͷͰ͸ͳ ͘ɺΫΤϦͷίετΛݟੵ΋Δͷ͕Ұൠత ▸ ਖ਼֬ͳίετ͸GraphQLΛ࣮ߦ͢Δ·ͰΘ͔Βͳ͍͕ɺϒϩοΫ͢ΔͨΊʹ࣮ߦ ͯ͠αʔόʹෛՙΛ͔͚ͯ͠·ͬͯ͸ݩ΋ࢠ΋ͳ͍ͷͰɺΫΤϦͷASTΛ࣮ߦલ ʹղੳͯ͠੩తʹܭࢉ͢Δ
  17. A rate limiting score on GitHub GraphQL API Refer: https://docs.github.com/en/graphql/overview/resource-limitations

    3. 60ݸͷϥϕϧΛऔಘ͍ͯ͠Δ͕ɺAPI͸߹ܭ5,000ݸͷજࡏతͳissueͦΕͧΕʹ ઀ଓͯ͠ϥϕϧͷϦετΛऔಘ͢Δඞཁ͕͋ΔɻͦͷͨΊϥϕϧͷϦΫΤετͷί ετ͸5,000 2. 50ݸͷissueΛऔಘ͍ͯ͠Δ͕ɺAPI͸100ݸͷϦϙδτϦͦΕͧΕʹ઀ଓͯ͠ issueͷϦετΛऔಘ͢Δඞཁ͕͋Γ·͢ɻͦͷͨΊɺIssueͷϦΫΤετͷίετ ͸100ͱ͢Δ 4. Total = 5,101(1 + 100 + 5000) 1. 100ݸͷrepositoryΛऔಘ͍ͯ͠Δ͕ɺAPI͸࠷ॳʹϏϡʔΞʔͷΞΧ΢ϯτʹ ઀ଓͯ͠repositoryͷϦετΛऔಘ͢Δඞཁ͕͋ΔɻͦͷͨΊɺrepositoryͷϦΫ Τετͷίετ͸1ͱ͢Δ 5. ߹ܭΛ100ͰׂΓɺ࠷ऴతͳείΞ͸ 51ͱ͢Δ
  18. Schema change management and versioning ▸ Private APIͰ΋ؾΛ෇͚Δ΂͖Ͱ͸͋Δ͕ɺPubic APIͰ͸APIͷޙํޓ׵ੑΛอͭ͜ͱ͕ಛʹॏཁɻҰ౓ ϦϦʔεͨ͠Schemaͷbreaking

    changes͸جຊతʹ͸ڐ͞Εͳ͍ ▸ RESTͰ͸urlͰόʔδϣϯΛදݱ͢Δ(/v2/patients)͜ͱ͕ଟ͍͕ɺGraphQLͰ͸εΩʔϚΛਐԽͤ͞Δ (Evolving schema)͜ͱͰޙํޓ׵ੑΛอͭ͜ͱ͕ਪ঑͞ΕΔ type Patient { id: ID! name: String age: Int } type Patient { id: ID! name: String age: Int @deprecated(reason: "Use birthDate instead") birthDate: String # ৽͍͠ϑΟʔϧυͷ௥Ճ }
  19. Problems of evolving schema ▸ ϑΟʔϧυ΍ϦϨʔγϣϯͷ௥ՃͰޙํޓ׵ੑΛอͯΔϨϕϧͷมߋͳΒྑ͍͕ɺͲ͏ͯ͠΋ޙํޓ׵ੑ Λอͯͳ͍มߋ͕ൃੜͨ͠৔߹ʹ͸Ͳ͏͢Δʁ ▸ ಛʹܕͷมߋ͸සൟʹൃੜ͠ಘΔͷͰɺઃܭ࣌ʹ͸৻ॏʹͳΔ΂͖ɻඒ͠͞ΛٻΊͯશͯΛnon-nullʹ ͍ͯͨ͠Γ͢Δͱɺnullable΁ͷมߋ͸breaking

    changesΛ൐͏ɻ ▸ Private APIͰ͋Ε͹ܕ໊͝ͱ৽͘͠ఆٛ͢Δ(PatientV2)͜ͱͰରԠ͠ɺΫϥΠΞϯτͷݺͼग़͠Λೖ Εସ͑ͨޙʹݹ͍APIΛ࢖Θͳ͘͢Ε͹ྑ͍ɻ͔͠͠Public APIͰಉ͡ରԠΛ͢ΔͱɺະདྷӬ߷ݹ͍Ϟ σϧʹରԠ͠ଓ͚Δ͜ͱʹͳͬͯ͠·͏ɻ ▸ @deprecatedʹͨ͠ϑΟʔϧυ͸͍ͭফͤΔͷ͔ʁ
  20. Evolving schema + Versioning GraphQL API ▸ جຊతʹ͸GraphQL͸Evolving schemaͷΞϓϩʔνͰػೳ֦ுΛ͍ͯ͘͠΂͖͚ͩͲɺͦΕ͸ඞͣ͠΋ Versioning͕Ͱ͖ͳ͍ͱ͍͏͜ͱΛҙຯ͠ͳ͍

    ▸ ࣮ࡍʹShopify͸GraphQL APIʹURL versioningΛ࠾༻͍ͯ͠Δ ▸ https://shopify.dev/docs/api/usage/versioning ▸ Ͳ͏ͯ͠΋ޙํޓ׵ੑΛอͯͳ͍มߋΛ͠ͳ͍ͱ͍͚ͳ͍৔߹ʹ͸ɺݹ͍εΩʔϚͱ৽͍͠εΩʔϚͷ྆ ํΛϝϯςφϯε͢ΔظؒΛઃ͚ͯɺݹ͍εΩʔϚͷαϙʔτظݶΛΞφ΢ϯε͢Δ͜ͱͰରԠ͢Δ ▸ ϝϯςφϯε͸ෳࡶʹͳΔͷͰɺඞͣ͠΋΍Δ΂͖ͱ͍͏࿩Ͱ΋ͳ͍ɻར༻Ϣʔβͱͷن໛ͱͷ݉Ͷ߹͍ ΋ݟͭͭɺબ୒ࢶΛ࣋ͭ͜ͱ͕େࣄ