実践GraphQL on Scala/Real world GraphQL on Scala

実践GraphQL on Scala/Real world GraphQL on Scala

Scala関西サミット2018 「実践GraphQL on Scala」の発表資料

- https://github.com/petitviolet/graphql_on_scala

93bc8fb48f57c11e417dad9d26a2fb8a?s=128

petitviolet

November 10, 2018
Tweet

Transcript

  1. ࣮ફ(SBQI2-PO4DBMB 4DBMBؔ੢4VNNJU !QFUJUWJPMFU  

  2. ͸͡Ίʹ w αϯϓϧίʔυ w IUUQTHJUIVCDPNQFUJUWJPMFUHSBQIRM@PO@TDBMB w ϦϯΫू w IUUQTHSBQIRMHJUIVCJPMFBSO w

    IUUQTXXXIPXUPHSBQIRMDPN w IUUQTTBOHSJBHSBQIRMPSHMFBSO 2
  3. Ͱɺ୭ʁ w খࢵ߂و w !QFUJUWJPMFU w 'SJOHFגࣜձࣾ w ؔ੢ग़਎ w

    ڈ೥ͷ4DBMBؔ੢Ͱ͸ϝλϓϩάϥϛϯάʹ͍ͭͯ 3
  4. (SBQI2- w ੝Γ্͕ͬͯΔ͚ͲΑ͘஌Βͳ͍ w ஌ͬͯΔ͚Ͳ4DBMBͰग़དྷΔͷʁ w ΍ͬͯΔ͚Ͳ΍Γํʹࣗ৴ͳ͍ 4

  5. ࠓ೔ͷΰʔϧ ʮ࣍ͷϓϩδΣΫτͰ͸ ɹ(SBQI2-Λ 4DBMBͰ ΍Γ͍ͨʂʯ 5

  6. ࠓ೔࿩͢͜ͱ࿩͞ͳ͍͜ͱ ࿩͢͜ͱ w (SBQI2-ʹ͍ͭͯ͟ʔͬͱ w 4DBMBͰͷ࣮ફʹ͍ͭͯ w ͳͥ(SBQI2-ͳͷ͔ɺͲ͏΍͍ͬͯΔͷ͔ w جຊతͳ࣮૷ͷ࢓ํ͔Βઃܭʹ͍ͭͯ

    ࿩͞ͳ͍͜ͱ w 4DBMBҎ֎Ͱͷ(SBQI2-ʹ͍ͭͯ w 4VCTDSJQUJPO! w ͦͷଞࡉ͔͍࢓༷ 6
  7. ࠓ೔࿩͢͜ͱ w (SBQI2-ͱ͸ w Ͳ͏cͳͥ (SBQI2-Λ࣮ફ͍ͯ͠Δͷ͔ w 4DBMBͰͷ(SBQI2- w 4DBMBͰͷ(SBQI2-࣮ફ

    7
  8. ɹ(SBQI2-ʁ 8

  9. 9 (SBQI2-JTBOPQFOTPVSDFEBUBRVFSZBOE NBOJQVMBUJPOMBOHVBHF BOEBSVOUJNFGPSGVMpMMJOH RVFSJFTXJUIFYJTUJOHEBUB https://en.wikipedia.org/wiki/GraphQL

  10. (SBQI2-ͱ͸ w 'BDFCPPL͕೥ࠒʹൃදͨ͠΋ͷ w ಺෦తʹ͸೥͘Β͍͔Β։ൃ w 2VFSZ-BOHVBHF w "1*ͷͨΊͷΫΤϦݴޠ w

    (SBQI2-ͱ͍͏࢓༷͕͋Δ w ެࣜIUUQTHSBQIRMPSH 10
  11. (SBQI2-ͱ͸ w ࢓༷͸'BDFCPPL͕ࡦఆ͍ͯ͠Δ w IUUQTGBDFCPPLHJUIVCJPHSBQIRM w IUUQTHJUIVCDPNGBDFCPPLHSBQIRM w ࢓༷ͱ͖ͯͬͪ͠ΓͱܾΊΒΕ͍ͯΔ w

    (JUIVCͰΦʔϓϯʹٞ࿦͞Ε͍ͯΔ w ֓೦Ͱ͸ͳ͍ 11
  12. (SBQI2-ͱ͸ w (JUIVC͕ʹ"1*Wͱͯ͠(SBQI2-Λ࠾༻ w ͔ͦ͜Β஌໊౓্͕͕ͬͨ 12

  13. 3&45GVMͱൺ΂ͯΈΔ w (SBQI2-੝Γ্͕ͬͯΔ 13

  14. (SBQI2-ͱ͸ 14 https://gql.foundation/

  15. (SBQI2-ΩςΔ 15

  16. (SBQI2-ͱ͸ w ܕγεςϜΛఏڙ͍ͯ͠Δ w "1*ΠϯλϑΣʔεͰ࢖༻͢ΔܕΛఆٛͰ͖Δ w ඞཁͳσʔλΛΫϥΠΞϯτ͔Β໰͍߹ΘͤΔ w ໰͍߹Θͤ ΫΤϦ

    ༻ͷݴޠ࢓༷ w ඞཁͳσʔλ ϑΟʔϧυ ͚ͩΛࢦఆͰ͖Δ w (SBQIJ2-͕ඇৗʹศར w ϒϥ΢βͰಈ͘*%&తͳ΋ͷ 16
  17. (SBQI2-ͷ༷ࢠ (SBQIJ2- 17

  18. (SBQI2-ͱ͸ w ؔ࿈͢ΔΦϒδΣΫτ΋·ͱΊͯऔಘՄೳ w ؔ࿈Λܕͱͯ͠දݱ͓͚ͯ͠͹ྑ͍ w υΩϡϝϯτ΋εΩʔϚ͔Βࣗಈੜ੒ 18

  19. 3&45GVM (SBQI2-ͱͷൺֱ 19 QSPT DPOT 3&45GVM w Α͘஌ΒΕ͍ͯΔ w Ϧιʔεͱ"1*ͷؔ࿈෇͚

    w )551ͱ૬ੑ͕ྑ͍ w Ϧιʔεͷઃܭ͕େม w ϦΫΤετճ਺૿͕͑ͪ (SBQI2- w *'Λܕఆٛग़དྷΔ w ϦΫΤετΛ࠷దԽग़དྷΔ w ΫΤϦͷ੹຿ΛΫϥΠΞϯτ ʹدͤΔ͜ͱ͕ग़དྷΔ w ஌ݟࣄྫ͕ཷ·͍ͬͯͳ͍ w )551ͱ૬ੑ͕ྑ͘ͳ͍ w ϑΝΠϧΛѻ͍ਏ͍ FHը૾
  20. (SBQI2-ͷ࢖͍Ͳ͜Ζ͸Ͳ͔͜ ओ؍ w ࢖͍΍͍͢ͷ͸಺෦޲͚"1* w ߟ͑Δ͜ͱ͕গͳ͘ɺγϯϓϧʹอͪ΍͍͢ w ֎෦޲͚͸·ͩ3&45ͷํ͕Ұൠత͔΋ w #''ʹ΋ྑͦ͞͏

    w ෳ਺ͷόοΫΤϯυΛଋͶͨ"1*Λ࡞Γ΍͍͢ w ඞཁͳσʔλΛඞཁͳ࣌ʹऔಘ w ෆඞཁͳͷʹຖճ໰͍߹ΘͤΔɺΛආ͚Δ 20
  21. ஫ҙ఺ w (SBQI2-͸ศར͕ͩɺۜͷ஄ؙͰ͸ͳ͍ w Α͘ग़དྷͨ3&45ͳΒɺ(SBQI2-Ͱ࣮ݱ͍ͯ͠Δ ͜ͱ͸શͯग़དྷΔ w ʮ(SBQI2-ʹ͔͠ग़དྷͳ͍͜ͱʯ͸ແ͍ w αϘͬͯ΋͋Δఔ౓࣮ݱͰ͖Δͷ͕(SBQI2-

    w "1*ͷܕදݱ υΩϡϝϯτࣗಈੜ੒ͳͲ w ౰વࣦΘΕΔ΋ͷ΋͋Δ 21
  22. (SBQI2-ͷUZQFTZTUFN ओͳొ৔ਓ෺͸͜ͷลΓ w OVMMBCMFOPOOVMM -JTU w 0CKFDU5ZQF w *OUFSGBDF w

    4DBMBS5ZQF w &OVN5ZQF w 6OJPO5ZQF w "SHVNFOU w *OQVU0CKFDU5ZQF 22
  23. (SBQI2-ͷUZQFTZTUFN ओͳొ৔ਓ෺͸͜ͷลΓ w OVMMBCMFOPOOVMM -JTU w 0CKFDU5ZQF w *OUFSGBDF w

    4DBMBS5ZQF w &OVN5ZQF w 6OJPO5ZQF w "SHVNFOU w *OQVU0CKFDU5ZQF 23
  24. OVMMBCMFOPOOVMM -JTU w OVMMBCMFOPOOVMM w AA͕͍ͭͨΒOPOOVMM w 0QUJPO<">A͔A"Aͷҧ͍ w -JTU

    w <9>Ͱ9ͷ-JTUʹͳΔ w <4USJOH>͸OVMMBCMFͳ4USJOHͷOVMMBCMFͳ-JTU type User { name: String! ←!Ͱnon-null projects: [Project!]! ←[x]ͰxͷList } 24 (SBQI2-ͷܕఆٛ
  25. OVMMBCMFOPOOVMM -JTU type User { name: String! projects: [Project!]! }

    25 case class User(name: String, projects: Seq[Project]) type User { name: String projects: [Project] } case class User( name: Option[String], projects: Option[Seq[Option[Project]]])
  26. ࣮ફʁ 26

  27. Ͳ͏࣮ફ͍ͯ͠Δͷ͔ w ޿ࠂγεςϜͷ؅ཧը໘޲͚"1* w ޿ࠂ഑৴ͷઃఆ΍ϨϙʔτΛӾཡͰ͖Δ w ಺෦޲͚"1*ͱͯ͠৴པͰ͖ΔΫϥΠΞϯτʹఏڙ w ΫϥΠΞϯτ͸&MNͰॻ͔Ε͍ͯΔ w

    ,VCFSOFUFTʹσϓϩΠ͍ͯ͠Δ w 2VFSZͱ.VUBUJPOͲͪΒ΋(SBQI2- w ϑΝΠϧͷ\Ξοϓ μ΢ϯ^ϩʔυ͸ඇ(SBQI2- 27
  28. ͳͥ(SBQI2-Λ࠾༻ͨ͠ͷ͔ w ؅ཧը໘࡞Δ͚ͲɺͲ͏΍ͬͯ࡞Δʁ w Ϧιʔεઃܭ͸೉͍͠ͷ͸ݟ͑ͯΔ w ʮ͜ͷը໘։͘ͷʹ"1*Λճ΋ୟ͘ͷʁʯ w ʮEPDTͱҧ͏Ϩεϙϯεฦͬͯ͘ΔͰ͚͢Ͳʁʯ w

    4XBHHFS+40/4DIFNB࢖͏ʜʁ w %%%ͷ3FQPTJUPSZ͕pOE#Z9YYͩΒ͚ w ը໘͔ΒͷϦΫΤετ͕ଟ༷ 28
  29. 3&45GVM (SBQI2-ͱͷൺֱ 29 QSPT DPOT 3&45GVM w Α͘஌ΒΕ͍ͯΔ w Ϧιʔεͱ"1*ͷؔ࿈෇͚

    w )551ͱ૬ੑ͕ྑ͍ w Ϧιʔεͷઃܭ͕େม w ϦΫΤετճ਺૿͕͑ͪ (SBQI2- w *'Λܕఆٛग़དྷΔ w ϦΫΤετΛ࠷దԽग़དྷΔ w ΫΤϦͷ੹຿ΛΫϥΠΞϯτ ʹدͤΔ͜ͱ͕ग़དྷΔ w ஌ݟࣄྫ͕ཷ·͍ͬͯͳ͍ w )551ͱ૬ੑ͕ྑ͘ͳ͍ w ϑΝΠϧΛѻ͍ਏ͍ FHը૾
  30. 3&45GVM (SBQI2-ͱͷൺֱ 30 QSPT DPOT 3&45GVM w Α͘஌ΒΕ͍ͯΔ w Ϧιʔεͱ"1*ͷؔ࿈෇͚

    w )551ͱ૬ੑ͕ྑ͍ w Ϧιʔεͷઃܭ͕େม w ϦΫΤετճ਺૿͕͑ͪ (SBQI2- w *'Λܕఆٛग़དྷΔ w ϦΫΤετΛ࠷దԽग़དྷΔ w ΫΤϦͷ੹຿ΛΫϥΠΞϯτ ʹدͤΔ͜ͱ͕ग़དྷΔ w ஌ݟࣄྫ͕ཷ·͍ͬͯͳ͍ w )551ͱ૬ੑ͕ྑ͘ͳ͍ w ϑΝΠϧΛѻ͍ਏ͍ FHը૾ ਏ͍ͱࢥ͍ͬͯΔͱ͜ΖΛ େମղܾͰ͖ͦ͏ͳݟࠐΈ
  31. 3&45GVM (SBQI2-ͱͷൺֱ 31 QSPT DPOT 3&45GVM w Α͘஌ΒΕ͍ͯΔ w Ϧιʔεͱ"1*ͷؔ࿈෇͚

    w )551ͱ૬ੑ͕ྑ͍ w Ϧιʔεͷઃܭ͕େม w ϦΫΤετճ਺૿͕͑ͪ (SBQI2- w *'Λܕఆٛग़དྷΔ w ϦΫΤετΛ࠷దԽग़དྷΔ w ΫΤϦͷ੹຿ΛΫϥΠΞϯτ ʹدͤΔ͜ͱ͕ग़དྷΔ w ஌ݟࣄྫ͕ཷ·͍ͬͯͳ͍ w )551ͱ૬ੑ͕ྑ͘ͳ͍ w ϑΝΠϧΛѻ͍ਏ͍ FHը૾ ੾Γ୓͍͍֮ͯ͘ޛͰ Կͱ͔ͳΓͦ͏
  32. ͳͥ(SBQI2-Λ࠾༻ͨ͠ͷ͔ w ͋ͱ͸λΠϛϯά w ৽͍͠ϓϩμΫτ։ൃͷػձ͕͋ͬͨ w (SBQI2-ʹϚονͨ͠ཁ݅ͩͬͨ w νʔϜϝϯόʹ΋(SBQI2-΍Γ͍ͨਓ͕͍ͨ w

    ϑϩϯτΤϯυʹ͍ͨͷ͕޾ӡ 32
  33. (SBQI2-Λ࠾༻ͯ͠Ͳ͏ʁ w "1*ઃܭͰ೰Ή͜ͱ͕গͳͯ͘޾ͤ w ΫϥΠΞϯτͷ͜ͱΑ͘஌Βͳͯ͘΋Կͱ͔ͳΔ w ཪػೳΛఏڙग़དྷ͍ͯΔ w (SBQIJ2-͕Ұ࣌తͳ؅ཧը໘ʹ w

    4DBMBͰඇৗʹ΍Γ΍͍͢ 33
  34. 4DBMBͰͷ(SBQI2- 34

  35. 4DBMBͰͷ(SBQI2- w TBOHSJBHSBQIRMTBOHSJB w (SBQI2-ϥΠϒϥϦ!0MFH*MZFOLP w 4DBMBͰ(SBQI2-΍ΔͳΒίϨ w ಛఆͷ8FC"QQ'SBNFXPSLʹґଘ͠ͳ͍ w

    *'͕+40/ʹͳ͍ͬͯΔͷͰࣗ༝ʹબ΂Δ w +T7BMVFతͳ΋ͷΛύʔεͯ͠(SBQI2-ΫΤϦ ͱ࣮ͯ͠ߦ͠ɺ+T7BMVFతͳ΋ͷΛฦ͢ 35
  36. implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields

    = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF type User { id: ID! name: String! status: UserStatus! projects: [Project!]! } 36 (SBQI2-ͷܕఆٛ 4BOHSJBͰͷఆٛ
  37. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 37
  38. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 38 0CKFDU5ZQFʹ ରԠ෇͚Δܕ
  39. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 39 $POUFYU ޙड़
  40. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 40 ϑΟʔϧυఆٛ
  41. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 41 AOBNFAϑΟʔϧυ
  42. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 42 SFTPMWFؔ਺ͷ$POUFYUʹ͸ w $POUFYU $UY  w 7BMVF $IBSBDUFS  ͕٧·͍ͬͯΔ
  43. type User { id: ID! name: String! status: UserStatus! projects:

    [Project!]! } implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 43 $POUFYUWBMVFͰ6TFSܕͷ ΠϯελϯεʹΞΫηεՄೳ
  44. implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields

    = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) type User { id: ID! name: String! status: UserStatus! projects: [Project!]! } 0CKFDU5ZQF 44 <">Λ-JTU5ZQF "5ZQF Ͱදݱ OPOOVMMͳ1SPKFDUͷ OPOOVMMͳ-JTU
  45. implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields

    = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) type User { id: ID! name: String! status: UserStatus! projects: [Project!]! } 0CKFDU5ZQF 45 SFTPMWFؔ਺ͷதͰ%#ΞΫηεͳͲͯ͠ ؔ࿈ΦϒδΣΫτΛऔಘग़དྷΔ
  46. implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields

    = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 46 6TFSOBNFΛऔಘ͢Δ2VFSZΛॻ͘ͱɺ VTFS5ZQFͷOBNFϑΟʔϧυͷSFTPMWFͷ݁ՌΛಘΔ
  47. implicit val userType: ObjectType[Ctx, User] = ObjectType(name = "User", fields

    = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) ) 0CKFDU5ZQF 47 %#ΞΫηε͕ඞཁʹͳΔϑΟʔϧυ͕͋ͬͯ΋ ͦͷϑΟʔϧυΛऔಘ͠ͳ͍ͳΒSFTPMWFؔ਺͕࣮ߦ͞ Εͣ%#ΞΫηε΋ൃੜ͠ͳ͍
  48. 0CKFDU5ZQF w ఆ͕ٛΊΜͲ͍͘͞ʁ 48 implicit val userType: ObjectType[Ctx, User] =

    ObjectType(name = "User", fields = fields[Ctx, User]( Field("id", IDType, resolve = { ctx => ctx.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { ctx => ctx.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byUser(ctx.value) }) ) )
  49. 0CKFDU5ZQF w ͦ͜ͰϚΫϩ w DBTFDMBTTͳΒ͍͍ײ͡ʹղܾͯ͘͠ΕΔ w ґଘ͢Δଞͷಠࣗܕ͸JNQMJDJUͰએݴ͓ͯ͘͜͠ͱ w ࠓճͳΒQSPKFDU5ZQFͱ͔ import

    sangria.macros.derive val userType: ObjectType[Ctx, User] = derive.deriveObjectType() 49
  50. &OVN5ZQF val userStatusType = EnumType[UserStatus]( "UserStatus", values = List( EnumValue[UserStatus]("Active",

    value = UserStatus.Active), EnumValue[UserStatus]("Paused", value = UserStatus.Paused) ) ) enum UserStatus { Active Paused } sealed abstract class UserStatus(val value: Int) object UserStatus { case object Active extends UserStatus(1) case object Paused extends UserStatus(2) } 50
  51. &OVN5ZQF΋ϚΫϩͰ val userStatusType: EnumType[UserStatus] = derive.deriveEnumType[UserStatus]() enum UserStatus { Active

    Paused } sealed abstract class UserStatus(val value: Int) object UserStatus { case object Active extends UserStatus(1) case object Paused extends UserStatus(2) } 51
  52. w &OVN5ZQF΋ϚΫϩͰಋग़Ͱ͖Δ w ATFBMFEAͱAPCKFDUAͷ૊Έ߹Θͤ &OVN5ZQF΋ϚΫϩͰ val userStatusType: EnumType[UserStatus] = derive.deriveEnumType[UserStatus]()

    sealed abstract class UserStatus(val value: Int) object UserStatus { case object Active extends UserStatus(1) case object Paused extends UserStatus(2) } 52
  53. w ީิʹͳΔܕ 0CKFDU5ZQF Λ-JTUͰ༩͑Δ 6OJPO5ZQF union Plan = Free |

    Standard | Enterprise object Plan { case class Free(startDate: ZonedDateTime) case class Standard(startDate: ZonedDateTime) case class Enterprise(userLimit: Int, startDate: ZonedDateTime) } implicit val planType: UnionType[Ctx] = { val free = derive.deriveObjectType[Ctx, Plan.Free]() val standard = derive.deriveObjectType[Ctx, Plan.Standard]() val enterprise = derive.deriveObjectType[Ctx, Plan.Enterprise]() UnionType("Plan", types = free :: standard :: enterprise :: Nil) } 53
  54. w ೖྗ஋ͱͯ͠+40/Λड͚औΔͨΊͷ΍ͭ w +40/Ͱड͚औͬͯ4DBMBͷΦϒδΣΫτʹม׵ *OQVU0CKFDU5ZQF mutation { UpdateTask(attributes: { taskId:

    "t1", taskName: "newName" }) { id } } case class UpdateTaskParam(taskId: String, taskName: Option[String], taskDescription: Option[String], assignedTo: Option[String]) 54
  55. w ͍ͭ͜΋ϚΫϩͰࣗಈಋग़ग़དྷΔ *OQVU0CKFDU5ZQF case class UpdateTaskParam(taskId: String, taskName: Option[String], taskDescription:

    Option[String], assignedTo: Option[String]) 55 implicit val paramJson = jsonFormat4(UpdateTaskParam.apply) val param = derive.deriveInputObjectType[UpdateTaskParam]() mutation { UpdateTask(attributes: { taskId: "t1", taskName: "newName" }) { id } }
  56. w &YFDVUPSʹεΩʔϚɺ࣮ߦ͍ͨ͠ΫΤϦɺ$POUFYU ͳͲΛ༩͑Δͱ+40/Λฦͯ͘͠ΕΔ (SBQI2-ͷ࣮ߦ val result: Future[JsValue] = Executor.execute[Ctx, Unit,

    JsObject]( schema, document, context ) 56 w ఆٛͨ͠(SBQI2-εΩʔϚ w ϦΫΤετͷ+40/Λύʔεͨ݁͠Ռ w ࣮ߦ͢ΔͨΊͷDPOUFYU
  57. 4DBMBͰͷ(SBQI2- w 4DBMBͱ૬ੑόπάϯ w ڧྗͳܕγεςϜͰ(SBQI2-ͷܕΛදݱ w ϚΫϩͰϘΠϥʔϓϨʔτ΋࡟ݮ w σϑΥϧτOPOOVMM࠷ߴ w

    0QUJPOΛ͚ͭͳ͍ͱOVMMBCMFʹͳΒͳ͍ w +40/ͱͷ૬ޓม׵ w DBTFDMBTT͕͋ΔͷͰࣗಈಋग़Ͱ͖Δ w TBOHSJBͷ͓͔͛Ͱे෼ઓ͑Δ 57
  58. (SBQI2-ͷ࣮ફ 58

  59. (SBQI2-Ͱͷ࣮ફฤ w (SBQI2-Λ4DBMB4BOHSJBͰͲ͏࣮૷͍ͯ͠Δ͔ w ͋͘·Ͱ͜͏΍࣮ͬͯફ͍ͯ͠Δɺͱ͍͏࿩ w ͍Ζ͍Ζ೰ΈϙΠϯτ΋ଟ͍ w (SBQI2-ͳΒͰ͸ͷ೰Έ΋͋Δ w

    খ͍͞΋ͷ͔Βେ͖͍΋ͷ·Ͱ͍Ζ͍Ζ 59
  60. (SBQI2-Ͱͷ࣮ફฤ w ΞʔΩςΫνϟ w εΩʔϚͷઃܭ w $POUFYUΛͲ͏͢Δ͔ w ೝূɺೝՄΛͲ͏͢Δ͔ w

    τϥϯβΫγϣϯ w ΤϥʔϋϯυϦϯά w / ໰୊ w FUD 60
  61. ΞʔΩςΫνϟ 61

  62. ΞʔΩςΫνϟ w ϓϩάϥϛϯάϨϕϧͰͷઃܭ w (SBQI2-ʹͲ͜·Ͱ੹຿Λ೚ͤΔ͔ w ͲΜͳײ͡Ͱ࡞Δͷ͕͍͍ͷ͔ 62

  63. ΞʔΩςΫνϟ HTTP GraphQL Business Logic Database Access 63

  64. w (SBQI2-͸)551ϨΠϠͱϏδωεϩδοΫͷؒ ΛऔΓ࣋ͭଘࡏͱͯ͠ઃܭ͢Δͷ͕ྑ͍ w (SBQI2-͸͋͘·Ͱ*'ͱͯ͠ར༻͢Δ ΞʔΩςΫνϟ HTTP GraphQL Business Logic

    Database Access 64
  65. w (SBQI2-࡞ऀͷ-FF#ZSPOࢯ΋ݴ͍ͬͯΔ w ೝূೝՄɺΩϟογϡ΍%#ΫΤϦͳͲ͸ (SBQI2-ϨΠϠͰ͸ͳͦ͘ͷԼͰ΍Δ΂͖ w ަ׵Մೳੑ্͕͕ͬͯকདྷ໾ʹཱͭ ΞʔΩςΫνϟ 65 https://www.youtube.com/watch?v=zVNrqo9XGOs

  66. w (SBQI2-ͷར఺͸2VFSZʹ͋ΒΘΕΔ w ΫϥΠΞϯτͷཁٻʹԠ͑Δॊೈͳ2VFSZ w .VUBUJPO͸ਖ਼௚ͦ͜·Ͱʜ ΞʔΩςΫνϟ 66

  67. w $234 w $PNNBOE2VFSZ3FTQPOTJCJMJUZ4FHSFHBUJPO ΞʔΩςΫνϟ 67 https://docs.microsoft.com/ja-jp/azure/architecture/guide/architecture-styles/cqrs

  68. ΞʔΩςΫνϟ HTTP GraphQL Business Logic Database Access 68

  69. ΞʔΩςΫνϟ 69 HTTP GraphQL UseCase/Application Database Access(DAO) Query Mutation Domain

    3FQPTJUPSZ Resolver
  70. w $234Λ؆ུԽͯ͠࠾༻͍ͯ͠Δ w ߋ৽ܥ͸%%%Ͱ͖ͬͪΓϞσϦϯά࣮૷ w ࢀরܥ͸TFMFDUͨ͠σʔλΛՃ޻͢Δ͚ͩ w (SBQI2-ͷ2VFSZ͸ͲΜͳσʔλΛඞཁͱ͞ΕΔ ͔ɺݺͼग़͞ΕΔ·ͰΘ͔Βͳ͍ w

    %%%3FQPTJUPSZ͸ݫ͍͠ w ఘΊ΋େࣄ w %BUB"DDFTT0CKFDUΛ࢖͏ ΞʔΩςΫνϟ 70
  71. w ͱ͸͍͑2VFSZܥͷSFTPMWFͰ%"0Λ௚઀ݺͿͱɺ %"0 ЋͳॲཧΛͨ͘͠ͳͬͨ࣌ʹࠔΔ w ΩϟογϡೖΕͨΓϖʔδωʔγϣϯͨ͠Γ w 3FTPMWFS૚Λ༻ҙ͓ͯ͘͠ͱྑ͍ w ಺෦Ͱ͸%"0

    Ћఔ౓ w SFTPMWFؔ਺͔Β࢖ΘΕΔ ΞʔΩςΫνϟ 71
  72. w 3FTPMWFSͷ࣮૷ྫ ΞʔΩςΫνϟ 72 Field("users", ListType(userType), resolve = { ctx

    => UserResolver.all()(ctx.ctx) }) object UserResolver { def all()(implicit ctx: GraphQLContext): Future[Seq[User]] = { withCache("user#all") { UserDao.findAll() } } }
  73. εΩʔϚ 73

  74. εΩʔϚ w ΫϥΠΞϯτʹͲΜͳεΩʔϚΛఏڙ͢Δ͔ w 3&45GVMͳ"1*ʹ͓͚Δ63-ઃܭʹ͍ۙ w ·͞ʹϦιʔεઃܭ w εΩʔϚͷେ͖ͳ෼͚ํ w

    ࢀরܥ͸2VFSZ w ߋ৽ܥ͸.VUBUJPO 74
  75. εΩʔϚ 2VFSZ w SPPUϨϕϧ͸ඇৗʹॏཁ w ʮ(SBQI2-͸ΫϥΠΞϯτ͕ඞཁͳσʔλ͚ͩ Λબ୒ͯ͠औಘ͢Δ͜ͱ͕ग़དྷΔʯͷى఺ w ͔͜͜Βؔ࿈Λ୧͍ͬͯ͘͜ͱʹͳΔ type

    Query { projects: [Project!]! tasks(status: TaskStatus): [Task!]! users(arg: UserSearchCondition): [User!]! } 75
  76. εΩʔϚ 2VFSZ w AWJFXFSA w ϦΫΤετ͖ͯͨ͠Ϣʔβࣗ਎ʹඥͮ͘৘ใ w ࣗ෼ࣗ਎ΛOPOOVMMͰఏڙͨ͠Γ type Query

    { projects: [Project!]! tasks: [Task!]! users: [User!]! viewer: ViewerQuery! } type ViewerQuery { self: User! assignedTasks: [Task!]! belongsTo: [Project!]! } 76
  77. εΩʔϚ 2VFSZ w AWJFXFSA w ϦΫΤετ͖ͯͨ͠Ϣʔβࣗ਎ʹඥͮ͘৘ใ w ࣗ෼ࣗ਎ΛOPOOVMMͰఏڙͨ͠Γ w (JUIVCWΛύΫΔ

    77
  78. εΩʔϚ .VUBUJPO w શͯSPPUϨϕϧʹฒ΂Δͷ͕Φεεϝ w ωετͨ͠.VUBUJPO͸࣮ߦॱং͕อূ͞Εͳ͍ mutation { CreateTask(…) {

    id } UpdateTask(…) { id } // CreateTaskͷޙʹ࣮ߦ͞ΕΔ } mutation { Task { Create(…) { id } Update(…) { id } // Createͷޙʹ࣮ߦ͞ΕΔอূ͸ͳ͍ } } 78
  79. εΩʔϚ .VUBUJPO w "SHVNFOUͱ*OQVU0CKFDU5ZQF͸͓޷ΈͰྑͦ͞͏ w 63-ύϥϝʔλPS+40/ͷΠϝʔδ w *OQVU0CKFDU5ZQF͸DBTFDMBTTʹύʔεग़དྷͯศར 79 *OQVU0CKFDU5ZQF

    "SHVNFOU
  80. όʔδϣχϯά 80

  81. όʔδϣχϯά w Α͋͘Δޡղ w ʮ(SBQI2-ͩͱόʔδϣχϯά͠ͳͯ͘Α͍ʯ 81

  82. όʔδϣχϯά w "1*ͷύεͱͯ͠όʔδϣχϯά͸ෆཁ w WHSBQIRM w WHSBQIRM 82 "

  83. όʔδϣχϯά w εΩʔϚͷόʔδϣχϯά͸ඞཁ w ϑΟʔϧυͷ௥ՃͷΈɺͩͱෆཁ w มߋ͸!EFQFSFDBUFE͚ͭͯɺมߋޙͷϑΟʔϧ υผ໊Ͱ௥Ճɺ͠͹Βͨ͘͠ΒมߋલͷΛফ͢ w ࡟আ͸!EFQFSFDBUFE͚ͭͯ͠͹Βͨ͘͠Βফ͢

    w %#ͷεΩʔϚͱಉ͡Α͏ͳϑϩʔ w ϑΟʔϧυ໊͸ඇৗʹॏཁ w มߋͮ͠Β͍ɺͱ͍͏ͷΛ೦಄ʹஔ͍͓ͯ͘ 83
  84. όʔδϣχϯά w 'JFMEʹAEFQSFDBUFE3FBTPOAΛ༩͑Δ 84 Field("name", StringType, resolve = { ctx

    => ctx.value.name }), Field("displayName", StringType, deprecationReason = Some("use `name` instead"), resolve = { ctx => ctx.value.name }),
  85. (SBQI2-ʹ͓͚Δ $POUFYU 85

  86. val userType: ObjectType[Ctx, User] = ObjectType[Ctx, User]( "User", fields[Ctx, User](

    Field("id", IDType, resolve = { _.value.id }), Field("name", StringType, resolve = { ctx: Context[Ctx, User] => ctx.value.name }), Field("status", userStatusType, resolve = { _.value.status }), Field("projects", ListType(projectType), resolve = { ctx => ProjectResolver.byIds(ctx.value.projectIds)(ctx.ctx) }) ) ) 0CKFDU5ZQF 86
  87. (SBQI2-ʹ͓͚Δ$POUFYU w (SBQI2-ͷܕʹ͓͚ΔSFTPMWFΛఆٛ͢Δࡍʹొ৔ ͢Δ$POUFYUʹԿΛ࣋ͨͤΔ͔ w ʮ$POUFYUʯͱ͍͏໊લ͕೉͍͠ w DPOUFYUVBMͳ৘ใͬͯԿʁ 87

  88. (SBQI2-ʹ͓͚Δ$POUFYU w ྫϩάΠϯதͷϢʔβ΍%#΁ͷΞΫηεΛ࣋ͭ context"WBMVFXIJDIJTQSPWJEFEUPFWFSZSFTPMWFSBOEIPMET JNQPSUBOUDPOUFYUVBMJOGPSNBUJPOMJLFUIFDVSSFOUMZMPHHFEJOVTFS  PSBDDFTTUPBEBUBCBTF IUUQTHSBQIRMPSHMFBSOFYFDVUJPOSPPUpFMETSFTPMWFST 88

  89. (SBQI2-ʹ͓͚Δ$POUFYU w %#ΞΫηεՄೳͳ4FSWJDF3FQPTJUPSZͳͲ 89 IUUQTTBOHSJBHSBQIRMPSHMFBSOJOQVUBOEDPOUFYUPCKFDUT

  90. (SBQI2-ʹ͓͚Δ$POUFYU w ͳΔ΄Ͳɺ%"0΍4FSWJDF΍3FQPTJUPSZΛ$POUFYU ͔ΒΞΫηεग़དྷΔΑ͏ʹͨ͠Βྑ͍Μͩͳʂ 90

  91. (SBQI2-ʹ͓͚Δ$POUFYU w ͳΔ΄Ͳɺ%"0΍4FSWJDF΍3FQPTJUPSZΛ$POUFYU ͔ΒΞΫηεग़དྷΔΑ͏ʹͨ͠Βྑ͍Μͩͳʂ 91 ͓͢͢Ί͠ͳ͍

  92. (SBQI2-ʹ͓͚Δ$POUFYU w %"0͕શͯͷϑΟʔϧυ͔Β$POUFYUܦ༝Ͱ޷͖ উखʹΞΫηεग़དྷΔΑ͏ʹͳͬͯ͠·ͬͯϨΠ Ϡʔ͕յΕΔ w SFTPMWFؔ਺ͰશͯΛ΍ͬͯ͠·͑Δঢ়ଶʹ w ݁Ռͱͯ͠(SBQI2-ϨΠϠ͕ް͘ͳΔ 92

  93. (SBQI2-ʹ͓͚Δ$POUFYU w $POUFYU͸ಉҰϦΫΤετ಺Ͱڞ௨ͯ͠ࢀরՄೳ w (SBQI2-ΫΤϦͷ࣮ߦ୯Ґ ϦΫΤετ Ͱڞ௨͠ ͯࢀর͍ͨ͠΋ͷΛ࣋ͨͤΔ΂͖ w ϩάΠϯϢʔβ

    w τϥϯβΫγϣϯ w &YFDVUJPO$POUFYU w FUD 93
  94. w $POUFYUͷ࣮૷ྫ w ϩάΠϯϢʔβ w τϥϯβΫγϣϯ %#4FTTJPO  w &YFDVUJPO$POUFYU

    class GraphQLContext( userOpt: Option[User], session: DBSession, ec: ExecutionContext ) (SBQI2-ʹ͓͚Δ$POUFYU 94
  95. ೝূೝՄ 95

  96. ೝূೝՄ w 8FCΞϓϦέʔγϣϯͳΒେମ͋Δ΍ͭ w ೝূ "VUIFOUJDBUJPO  w ୭͔Λಛఆ͢Δ w

    ೝՄ "VUIPSJ[BUJPO  w ݖݶΛ֬ೝ͢Δ w (SBQI2-ͱͲ͏૊Έ߹ΘͤΔ΂͖͔ɺͱ͍͏࿩ 96
  97. ೝূ w ೝূͨ݁͠Ռ͸શମͰ࢖͍ճ͍ͨ͠ w ෳ਺ͷ2VFSZ.VUBUJPOΛಉ࣌ʹ࣮ߦ͢Δࡍʹɺ ผʑʹೝূΛ͢Δͷ͸ྲྀੴʹڐ༰Ͱ͖ͳ͍ w ೝূॲཧͦͷ΋ͷ͸(SBQI2-ͱ͸ؔ܎ͳ͍ w $PPLJF࢖͓͏͕+85࢖͓͏͕͓޷͖ʹ

    97
  98. ೝূ w (SBQI2-ͷ$POUFYUΛ࡞੒͢ΔࡍʹೝূΛߦ͏ w ೝূ݁ՌΛ$POUFYUʹೖΕͯ͠·͑͹ڞ༗ग़དྷΔ 98 object GraphQLContext { def

    create(userIdOpt: Option[String])( implicit ec: ExecutionContext, s: DBSession): GraphQLContext = { userIdOpt.flatMap { userId => authenticate(userId).map { user => new GraphQLContext(Some(user), ec, s) } } getOrElse { new GraphQLContext(None, ec, s) } } private def authenticate(userId: String): Option[User] = ??? } GBDUPSZͰೝূͭͭ͠ੜ੒
  99. ೝূ w ೝূΛඞཁͱ͢Δ2VFSZ.VUBUJPOΛ࣮૷͢Δ w ೝূॲཧͦͷ΋ͷ͸(SBQI2-ͱ͸੾Γ཭͢ w .JEEMFXBSF͕࢖͏ͱڧ੍Ͱ͖ͯศར w ΫΤϦ࣮ߦͷCFGPSFBGUFSʹॲཧΛࠩ͠ࠐΊΔ w

    TBOHSJBFYFDVUJPO഑ԼͷΫϥεΛར༻͢Δ w 'JFME5BH w .JEEMFXBSF.JEEMFXBSF#FGPSF'JFME 99
  100. w ೝূඞਢʹ͍ͨ͠pFMEͷఆٛʹUBHΛ෇༩͢Δ w &YFDVUPSFYFDVUFʹNJEEMFXBSFͱͯ͠ "VUIFOUJDBUJPO'JMUFSΛ༩͑Ε͹ಈ͘ ೝূ val self: Field[Ctx, User]

    = Field( "self", userType, tags = Middlewares.RequireAuthentication :: Nil, resolve = { ctx => ctx.ctx.loggedInUser } ) ೝূඞਢͳ͜ͱΛλάͰ໌ࣔ Executor.execute[Ctx, Unit, JsObject]( schema, queryDocument, context, middleware = AuthenticationFilter :: Nil ) 100
  101. ೝূ type Ctx = GraphQLContext case object RequireAuthentication extends FieldTag

    object AuthenticationFilter extends MiddlewareBeforeField[Ctx] { override type QueryVal = Unit override type FieldVal = Unit private type MCtx = MiddlewareQueryContext[Ctx, _, _] override def beforeQuery(context: MCtx) = () override def afterQuery(queryVal: QueryVal, context: MCtx) = () override def beforeField(queryVal: QueryVal, mctx: MCtx, ctx: Context[Ctx, _]) = { if (ctx.field.tags contains RequireAuthentication) { if (!ctx.ctx.isLoggedIn) { throw AuthenticationError("must logged in!") } } continue } } 101
  102. type Ctx = GraphQLContext case object RequireAuthentication extends FieldTag object

    AuthenticationFilter extends MiddlewareBeforeField[Ctx] { override type QueryVal = Unit override type FieldVal = Unit private type MCtx = MiddlewareQueryContext[Ctx, _, _] override def beforeQuery(context: MCtx) = () override def afterQuery(queryVal: QueryVal, context: MCtx) = () override def beforeField(queryVal: QueryVal, mctx: MCtx, ctx: Context[Ctx, _]) = { if (ctx.field.tags contains RequireAuthentication) { if (!ctx.ctx.isLoggedIn) { throw AuthenticationError("must logged in!") } } continue } } ೝূ ೝূඞਢͳϑΟʔϧυʹ෇༩͢Δ5BH 102
  103. type Ctx = GraphQLContext case object RequireAuthentication extends FieldTag object

    AuthenticationFilter extends MiddlewareBeforeField[Ctx] { override type QueryVal = Unit override type FieldVal = Unit private type MCtx = MiddlewareQueryContext[Ctx, _, _] override def beforeQuery(context: MCtx) = () override def afterQuery(queryVal: QueryVal, context: MCtx) = () override def beforeField(queryVal: QueryVal, mctx: MCtx, ctx: Context[Ctx, _]) = { if (ctx.field.tags contains RequireAuthentication) { if (!ctx.ctx.isLoggedIn) { throw AuthenticationError("must logged in!") } } continue } } ೝূ 0CKFDU5ZQF౳ͷpFMEʹࠩ͠ࠐΉॲཧ 103
  104. type Ctx = GraphQLContext case object RequireAuthentication extends FieldTag object

    AuthenticationFilter extends MiddlewareBeforeField[Ctx] { override type QueryVal = Unit override type FieldVal = Unit private type MCtx = MiddlewareQueryContext[Ctx, _, _] override def beforeQuery(context: MCtx) = () override def afterQuery(queryVal: QueryVal, context: MCtx) = () override def beforeField(queryVal: QueryVal, mctx: MCtx, ctx: Context[Ctx, _]) = { if (ctx.field.tags contains RequireAuthentication) { if (!ctx.ctx.isLoggedIn) { throw AuthenticationError("must logged in!") } } continue } } ೝূ 3FRVJSF"VUIFOUJDBUJPOλά͕෇͍ͯΔͷʹ ϩάΠϯͯ͠ͳ͍৔߹͸ྫ֎Λૹग़ 104
  105. ೝՄ w ೝՄ͸ΞϓϦέʔγϣϯ૚Ͱߦ͏ w (SBQI2-ʹ͸ؔ܎ͳ͍࿩ͳͷͰׂѪ 105

  106. %#τϥϯβΫγϣϯ 106

  107. %#τϥϯβΫγϣϯ w Ͳͷ୯ҐͰτϥϯβΫγϣϯΛ࣮ߦ͢Δ͔ w ͍ͭ#FHJO͍ͯͭ͠$PNNJU3PMMCBDL͢Δ͔ w (SBQI2-ΫΤϦ͸ෳ਺ͷ2VFSZ.VUBUJPOΛ·ͱ Ί࣮ͯߦ͢Δ͜ͱ͕ग़དྷΔ w ಛʹ.VUBUJPO͸ߋ৽ܥͳͷͰτϥϯβΫγϣϯ

    Λผʑʹ͍ͨ͠ͱ͍͏ཉٻ͸͋Δ͸ͣ 107
  108. %#τϥϯβΫγϣϯ w ݁࿦ɺϦΫΤετʹରͯ͠τϥϯβΫγϣϯ w ຊ౰ʹτϥϯβΫγϣϯΛผʹ͍ͨ͠ૢ࡞ΛϦ ΫΤετʹ৐ͤͯ·ͱΊ࣮ͯߦ͢Δ͜ͱ͕͋Δ ͩΖ͏͔ʁ 108

  109. %#τϥϯβΫγϣϯ w τϥϯβΫγϣϯͷ੹຿͕(SBQI2-ϨΠϠʹʜ w $POUFYUʹ࣋ͨͤͯΫΤϦؒͰڞ༗͢Δ w ઃܭతʹ͸ͪΐͬͱඍົͳؾ࣋ͪ w ԿΛ༏ઌ͢Δ͔Ͱܾఆ͢Δ΂͖ 109

  110. ΤϥʔϋϯυϦϯά 110

  111. ΤϥʔϋϯυϦϯά w (SBQI2-Ͱ͸εςʔλείʔυ͸جຊతʹ w Τϥʔ͸ϨεϙϯεϘσΟʹೖΕͯฦ٫͞ΕΔ 111

  112. ΤϥʔϋϯυϦϯά w ෳ਺ͷ2VFSZ.VUBUJPOΛ࣮ߦͨ͠ࡍʹɺͲΕ͔ ͭͰ΋ࣦഊͨ͠Βશମ͕Τϥʔʹͳͬͯ͠·͏ ͱ͍͏໰୊͕͋Δ w ྫɿτοϓϖʔδͷදࣔʹඞཁͳϦιʔεΛ· ͱΊͯऔಘͨ͠ΒɺҰ෦෼͚ͩऔಘʹࣦഊͨ͠ ͚ͩͰશମ͕ਅͬനʹͳΔ w

    ϚΠΫϩαʔϏε࣌୅ʹ͙ͦΘͳ͍ײ w #''ʹ࢖͍ਏ͍ʜ 112
  113. ΤϥʔϋϯυϦϯά w ͍Ζ͍Ζબ୒ࢶ͸͋Δ w શ෦औಘͰ͖ͳ͍ͱҙຯແ͍୯ҐͰΫΤϦൃߦ w গʑࣦഊͯ͠΋ϦτϥΠ͢Ε͹͑͑΍Μ w FUD w

    ੒ޭࣦഊΛಉډͤ͞Δͷ͸͔ͳΓେมͦ͏ w 6OJPO5ZQFʹͯ͠੒ޭࣦഊΛදݱ͢Δͱ͔ʁ w SFTPMWFͰྫ֎͕౤͛ΒΕͳ͍Α͏ʹέΞ͕ඞཁ 113
  114. ΤϥʔϋϯυϦϯά w ͰɺͲ͏͍ͯ͠Δ͔ w Ұ෦ͷΤϥʔΛશମͷΤϥʔͱ͍ͯ͠Δ w ࣮૷ίετͱͷ݉Ͷ߹͍ w Ұ෦Ͱ΋ࣦഊ͢ΔͷͬͯԿ͔͕͓͔͍͠ w

    #''ͳΒಛఆͷΞϓϦ͕ෆ௨ʹͳΔՄೳੑ΋ w ୯ҰΞϓϦͳΒ૝ఆ͢Δඞཁͳ͍ 114
  115. ΤϥʔϋϯυϦϯά w Τϥʔͷछྨ͚ͩΘ͔ΔΑ͏ʹ w ϦΫΤετෆਖ਼αʔόΤϥʔೝূΤϥʔ sealed abstract class ErrorType(val value:

    String) object ErrorType { case object BadRequest extends ErrorType("BadRequest") case object ServerError extends ErrorType("ServerError") case object AuthError extends ErrorType("AuthError") } implicit val error = derive.deriveEnumType[ErrorType]() 115
  116. ΤϥʔϋϯυϦϯά w Τϥʔͷछྨ͚ͩΘ͔ΔΑ͏ʹ w ϦΫΤετෆਖ਼αʔόΤϥʔೝূΤϥʔ sealed abstract class ErrorType(val value:

    String) object ErrorType { case object BadRequest extends ErrorType("BadRequest") case object ServerError extends ErrorType("ServerError") case object AuthError extends ErrorType("AuthError") } implicit val error = derive.deriveEnumType[ErrorType]() def handleException(m: ResultMarshaller, msg: String, tp: ErrorType) = HandledException( msg, additionalFields = Map("type" -> m.enumNode(tp.value, error.name)), addFieldsInExtensions = false, addFieldsInError = true ) )BOEMFE&YDFQUJPOΛ࢖͑͹ (SBQI2-ͬΆ͍ΤϥʔϝοηʔδΛฦ٫Ͱ͖Δ 116
  117. ΤϥʔϋϯυϦϯά w Τϥʔͷछྨ͚ͩΘ͔ΔΑ͏ʹ w ϦΫΤετෆਖ਼αʔόΤϥʔೝূΤϥʔ sealed abstract class ErrorType(val value:

    String) object ErrorType { case object BadRequest extends ErrorType("BadRequest") case object ServerError extends ErrorType("ServerError") case object AuthError extends ErrorType("AuthError") } implicit val error = derive.deriveEnumType[ErrorType]() def handleException(m: ResultMarshaller, msg: String, tp: ErrorType) = HandledException( msg, additionalFields = Map("type" -> m.enumNode(tp.value, error.name)), addFieldsInExtensions = false, addFieldsInError = true ) 117
  118. ΤϥʔϋϯυϦϯά w ϋϯυϦϯά͢Δ΂͖Τϥʔ͕ଟ͍ w +40/ͷTZOUBYΤϥʔ w ΫΤϦͷTZOUBYΤϥʔɺWBMJEBUJPOΤϥʔ w ϏδωεϩδοΫ্ͷΤϥʔ w

    ͦͷଞɺUISPX͞ΕͨΤϥʔ w Ͳ͏΍ͬͯϋϯυϦϯά͢Δʁ w &YDFQUJPO)BOEMFSΛؤு࣮ͬͯ૷͢Δ w &YFDVUPSFYFDVUFͷSFDPWFSͰ΋ؤுΔ 118
  119. ΤϥʔϋϯυϦϯά private val exceptionHandler = { def handleException(m: ResultMarshaller, msg:

    String, tp: ErrorType) = HandledException( msg, additionalFields = Map("type" -> m.enumNode(tp.value, errorType.name)), addFieldsInExtensions = false, addFieldsInError = true ) val onException: PartialFunction[(ResultMarshaller, Throwable), HandledException] = { case (m, qa: QueryAnalysisError) => logger.warn(s"QueryAnalysisError occurred. msg = ${qa.getMessage}") handleException(m, qa.getMessage, ErrorType.BadRequest) case (m, se: SyntaxError) => logger.info(s"SyntaxError occurred. msg = ${se.getMessage()}") handleException(m, se.getMessage, ErrorType.BadRequest) case (m, ewr: ErrorWithResolver) => logger.error(s"ErrorWithResolver occurred. msg = ${ewr.getMessage}", ewr) handleException(m, ewr.getMessage, ErrorType.ServerError) case (m, AuthenticationError(msg)) => logger.info(s"AuthenticationError occurred. msg = ${msg}") handleException(m, msg, ErrorType.BadRequest) case (m, NotFoundException(msg)) => logger.info(s"NotFoundException occurred. msg = ${msg}") handleException(m, msg, ErrorType.BadRequest) case (m, t: Throwable) => logger.error(s"unknown server error occurred. msg = ${t.getMessage}", t) handleException(m, t.getMessage, ErrorType.ServerError) } val onViolation: PartialFunction[(ResultMarshaller, Violation), HandledException] = { case (m, v) => logger.warn(s"Violation error occurred. msg = ${v.errorMessage}") handleException(m, v.errorMessage, ErrorType.BadRequest) } val onUserFacingError : PartialFunction[(ResultMarshaller, UserFacingError), HandledException] = { case (m, v: WithViolations) => logger.warn(s"WithViolations occurred. msg = ${v.getMessage()}") handleException(m, v.getMessage, ErrorType.BadRequest) case (m, ie: InternalError) => logger.error(s"InternalError occurred. msg = ${ie.getMessage()}", ie) handleException(m, ie.getMessage, ErrorType.ServerError) case (m, ee: ExecutionError) => logger.error(s"ExecutionError occurred. msg = ${ee.getMessage()}", ee) handleException(m, ee.getMessage, ErrorType.ServerError) case (m, ufe) => logger.warn(s"UserFacingError occurred. msg = ${ufe.getMessage()}") handleException(m, ufe.getMessage, ErrorType.BadRequest) } ExceptionHandler(onException, onViolation, onUserFacingError) } Future.fromTry(QueryParser.parse(document)) zip GraphQLContext.create(userIdOpt) flatMap { case (queryDocument: Document, context: GraphQLContext) => val result: Future[JsValue] = Executor.execute[Ctx, Unit, JsObject]( schema, queryDocument, context, exceptionHandler = exceptionHandler, operationName = operation, variables = vars, middleware = Middlewares.value ) result .map { jsValue => OK -> jsValue } .recover { case error: QueryAnalysisError => BadRequest -> error.resolveError case error: ErrorWithResolver => InternalServerError -> error.resolveError } } recover { case v: SyntaxError => BadRequest -> new Exception(v.getMessage()) with ErrorWithResolver with WithViolations { override def exceptionHandler: ExceptionHandler = GraphQLServer.exceptionHandler override def violations: Vector[Violation] = Vector.empty }.resolveError case v: ValidationError => BadRequest -> v.resolveError case NonFatal(t) => InternalServerError -> InternalError(t, GraphQLServer.exceptionHandler).resolveError } 119
  120. ΤϥʔϋϯυϦϯά private val exceptionHandler = { def handleException(m: ResultMarshaller, msg:

    String, tp: ErrorType) = HandledException( msg, additionalFields = Map("type" -> m.enumNode(tp.value, errorType.name)), addFieldsInExtensions = false, addFieldsInError = true ) val onException: PartialFunction[(ResultMarshaller, Throwable), HandledException] = { case (m, qa: QueryAnalysisError) => logger.warn(s"QueryAnalysisError occurred. msg = ${qa.getMessage}") handleException(m, qa.getMessage, ErrorType.BadRequest) case (m, se: SyntaxError) => logger.info(s"SyntaxError occurred. msg = ${se.getMessage()}") handleException(m, se.getMessage, ErrorType.BadRequest) case (m, ewr: ErrorWithResolver) => logger.error(s"ErrorWithResolver occurred. msg = ${ewr.getMessage}", ewr) handleException(m, ewr.getMessage, ErrorType.ServerError) case (m, AuthenticationError(msg)) => logger.info(s"AuthenticationError occurred. msg = ${msg}") handleException(m, msg, ErrorType.BadRequest) case (m, NotFoundException(msg)) => logger.info(s"NotFoundException occurred. msg = ${msg}") handleException(m, msg, ErrorType.BadRequest) case (m, t: Throwable) => logger.error(s"unknown server error occurred. msg = ${t.getMessage}", t) handleException(m, t.getMessage, ErrorType.ServerError) } val onViolation: PartialFunction[(ResultMarshaller, Violation), HandledException] = { case (m, v) => logger.warn(s"Violation error occurred. msg = ${v.errorMessage}") handleException(m, v.errorMessage, ErrorType.BadRequest) } val onUserFacingError : PartialFunction[(ResultMarshaller, UserFacingError), HandledException] = { case (m, v: WithViolations) => logger.warn(s"WithViolations occurred. msg = ${v.getMessage()}") handleException(m, v.getMessage, ErrorType.BadRequest) case (m, ie: InternalError) => logger.error(s"InternalError occurred. msg = ${ie.getMessage()}", ie) handleException(m, ie.getMessage, ErrorType.ServerError) case (m, ee: ExecutionError) => logger.error(s"ExecutionError occurred. msg = ${ee.getMessage()}", ee) handleException(m, ee.getMessage, ErrorType.ServerError) case (m, ufe) => logger.warn(s"UserFacingError occurred. msg = ${ufe.getMessage()}") handleException(m, ufe.getMessage, ErrorType.BadRequest) } ExceptionHandler(onException, onViolation, onUserFacingError) } Future.fromTry(QueryParser.parse(document)) zip GraphQLContext.create(userIdOpt) flatMap { case (queryDocument: Document, context: GraphQLContext) => val result: Future[JsValue] = Executor.execute[Ctx, Unit, JsObject]( schema, queryDocument, context, exceptionHandler = exceptionHandler, operationName = operation, variables = vars, middleware = Middlewares.value ) result .map { jsValue => OK -> jsValue } .recover { case error: QueryAnalysisError => BadRequest -> error.resolveError case error: ErrorWithResolver => InternalServerError -> error.resolveError } } recover { case v: SyntaxError => BadRequest -> new Exception(v.getMessage()) with ErrorWithResolver with WithViolations { override def exceptionHandler: ExceptionHandler = GraphQLServer.exceptionHandler override def violations: Vector[Violation] = Vector.empty }.resolveError case v: ValidationError => BadRequest -> v.resolveError case NonFatal(t) => InternalServerError -> InternalError(t, GraphQLServer.exceptionHandler).resolveError } ΍Δ͔͠ͳ͍ 120
  121. / ໰୊ 121

  122. / ໰୊ w (SBQI2-Ͱ͸ϦΫΤετΛճʹݻఆͰ͖Δ w ؔ࿈Λ୧ͬͯඞཁͳσʔλΛऔಘ͢Δ w ݁Ռͱͯ͠/ ͳ42-͕ൃߦ͞Ε͕ͪ 122

  123. / ໰୊ QSPKFDU͕/ݸ͋Ε͹ VTFST΋/ճऔಘ͢Δɺ ·͞ʹ/ ͳঢ়ଶ 123

  124. / ໰୊ w / Λճආ͢ΔͨΊͷ࢓૊Έ͕ఏڙ͞Ε͍ͯΔ w %FGFSSFE3FTPMWFS w 'FUDIFS w

    SFTPMWF͢ΔͷΛ஗Βͤͯɺ·ͱΊͯऔಘͰ͖Δ΋ ͷΛ·ͱΊͯऔಘ͢ΔΑ͏ʹ͢Δ w pOE#Z*E /ΛpOE#Z*ETʹ͢Δ͚ͩͳΠϝʔδ w Ωϟογϡͨ͠Γ΋Մೳ 124
  125. object UserResolver { private implicit lazy val hasId: HasId[User, UserId]

    = HasId(_.id) val userFetcher: Fetcher[GraphQLContext, User, User, UserId] = { Fetcher.apply { (ctx: GraphQLContext, ids: Seq[UserId]) => UserDao.findByIds(ids)(ctx.ec) } } def byId(userId: UserId)( implicit ctx: GraphQLContext): DeferredValue[GraphQLContext, User] = { DeferredValue(userFetcher.defer(userId)) } } / ໰୊ 125
  126. object UserResolver { private implicit lazy val hasId: HasId[User, UserId]

    = HasId(_.id) val userFetcher: Fetcher[GraphQLContext, User, User, UserId] = { Fetcher.apply { (ctx: GraphQLContext, ids: Seq[UserId]) => UserDao.findByIds(ids)(ctx.ec) } } def byId(userId: UserId)( implicit ctx: GraphQLContext): DeferredValue[GraphQLContext, User] = { DeferredValue(userFetcher.defer(userId)) } } / ໰୊ 126 4FR<6TFS*E>4FR<6TFS>ͳॲཧ
  127. object UserResolver { private implicit lazy val hasId: HasId[User, UserId]

    = HasId(_.id) val userFetcher: Fetcher[GraphQLContext, User, User, UserId] = { Fetcher.apply { (ctx: GraphQLContext, ids: Seq[UserId]) => UserDao.findByIds(ids)(ctx.ec) } } def byId(userId: UserId)( implicit ctx: GraphQLContext): DeferredValue[GraphQLContext, User] = { DeferredValue(userFetcher.defer(userId)) } } / ໰୊ 127 4FR<6TFS*E>4FR<6TFS>ͳॲཧΛ ϥοϓ͢Δ'FUDIFS
  128. object UserResolver { private implicit lazy val hasId: HasId[User, UserId]

    = HasId(_.id) val userFetcher: Fetcher[GraphQLContext, User, User, UserId] = { Fetcher.apply { (ctx: GraphQLContext, ids: Seq[UserId]) => UserDao.findByIds(ids)(ctx.ec) } } def byId(userId: UserId)( implicit ctx: GraphQLContext): DeferredValue[GraphQLContext, User] = { DeferredValue(userFetcher.defer(userId)) } } / ໰୊ 128 'FUDIFSEFGFSͰ*%Λ౉͢ͱ ͍͍ײ͡ʹ/ Λ௵ͯ͘͠ΕΔ
  129. / ໰୊ 129 w ؔ࿈Λ୧Δ/ Λղফ͢Δͷ΋༻ҙ͞Ε͍ͯΔ object UserResolver { private

    val usersByProjects: Relation[User, User, ProjectId] = Relation[User, ProjectId]("byProjects", { u: User => u.projectIds }) lazy val userFetcherByProject: Fetcher[GraphQLContext, User, User, UserId] = { Fetcher.relOnly { (ctx: GraphQLContext, rel: RelationIds[User]) => val ids: Seq[ProjectId] = rel.apply(usersByProjects) UserDao.findByProjectIds(ids)(ctx.ec) } } def findByProjectId(projectId: ProjectId)( implicit ctx: GraphQLContext): DeferredValue[GraphQLContext, Seq[User]] = { DeferredValue(userFetcherByProject.deferRelSeq(usersByProjects, projectId)) } }
  130. ·ͱΊ 130

  131. ·ͱΊ w (SBQI2-ศརͩ͠Φεεϝ w ۜͷ஄ؙͰ͸ͳ͍ ೋ౓໨  w ࣍ͷϓϩδΣΫτͰ΋࠾༻͍ͨ͠ w

    3&45΍ͬͯͨࠒͱ೰ΉϙΠϯτ͕มΘΔ w Ͳ͕ͬͪྑ͍ɺͱ͍͏࿩Ͱ͸ͳ͍ w 4DBMBͱ΋૬ੑ͸ྑ͍ w 4BOHSJBΛ࢖͑͹े෼ઓ͑Δ 131
  132. ࣮ફͰ͖ͦ͏Ͱ͠ΐ͏͔ʁ 132

  133. ࠓ೔ͷΰʔϧ ʮ࣍ͷϓϩδΣΫτͰ͸ ɹ(SBQI2-Λ 4DBMBͰ ΍Γ͍ͨʂʯ 133

  134. ࣮ફ(SBQI2-PO4DBMB 4DBMBؔ੢4VNNJU !QFUJUWJPMFU