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

実践GraphQL on Scala/Real world GraphQL on Scala

petitviolet
November 10, 2018

実践GraphQL on Scala/Real world GraphQL on Scala

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

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

petitviolet

November 10, 2018
Tweet

More Decks by petitviolet

Other Decks in Programming

Transcript

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

    ༻ͷݴޠ࢓༷ w ඞཁͳσʔλ ϑΟʔϧυ ͚ͩΛࢦఆͰ͖Δ w (SBQIJ2-͕ඇৗʹศར w ϒϥ΢βͰಈ͘*%&తͳ΋ͷ 16
  2. 3&45GVM (SBQI2-ͱͷൺֱ 19 QSPT DPOT 3&45GVM w Α͘஌ΒΕ͍ͯΔ w Ϧιʔεͱ"1*ͷؔ࿈෇͚

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

    w ෳ਺ͷόοΫΤϯυΛଋͶͨ"1*Λ࡞Γ΍͍͢ w ඞཁͳσʔλΛඞཁͳ࣌ʹऔಘ w ෆඞཁͳͷʹຖճ໰͍߹ΘͤΔɺΛආ͚Δ 20
  4. (SBQI2-ͷUZQFTZTUFN ओͳొ৔ਓ෺͸͜ͷลΓ w OVMMBCMFOPOOVMM -JTU w 0CKFDU5ZQF w *OUFSGBDF w

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

    4DBMBS5ZQF w &OVN5ZQF w 6OJPO5ZQF w "SHVNFOU w *OQVU0CKFDU5ZQF 23
  6. 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-ͷܕఆٛ
  7. 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]]])
  8. 3&45GVM (SBQI2-ͱͷൺֱ 29 QSPT DPOT 3&45GVM w Α͘஌ΒΕ͍ͯΔ w Ϧιʔεͱ"1*ͷؔ࿈෇͚

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

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

    w )551ͱ૬ੑ͕ྑ͍ w Ϧιʔεͷઃܭ͕େม w ϦΫΤετճ਺૿͕͑ͪ (SBQI2- w *'Λܕఆٛग़དྷΔ w ϦΫΤετΛ࠷దԽग़དྷΔ w ΫΤϦͷ੹຿ΛΫϥΠΞϯτ ʹدͤΔ͜ͱ͕ग़དྷΔ w ஌ݟࣄྫ͕ཷ·͍ͬͯͳ͍ w )551ͱ૬ੑ͕ྑ͘ͳ͍ w ϑΝΠϧΛѻ͍ਏ͍ FHը૾ ੾Γ୓͍͍֮ͯ͘ޛͰ Կͱ͔ͳΓͦ͏
  11. 4DBMBͰͷ(SBQI2- w TBOHSJBHSBQIRMTBOHSJB w (SBQI2-ϥΠϒϥϦ!0MFH*MZFOLP w 4DBMBͰ(SBQI2-΍ΔͳΒίϨ w ಛఆͷ8FC"QQ'SBNFXPSLʹґଘ͠ͳ͍ w

    *'͕+40/ʹͳ͍ͬͯΔͷͰࣗ༝ʹબ΂Δ w +T7BMVFతͳ΋ͷΛύʔεͯ͠(SBQI2-ΫΤϦ ͱ࣮ͯ͠ߦ͠ɺ+T7BMVFతͳ΋ͷΛฦ͢ 35
  12. 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Ͱͷఆٛ
  13. 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
  14. 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ʹ ରԠ෇͚Δܕ
  15. 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 ޙड़
  16. 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 ϑΟʔϧυఆٛ
  17. 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ϑΟʔϧυ
  18. 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  ͕٧·͍ͬͯΔ
  19. 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ܕͷ ΠϯελϯεʹΞΫηεՄೳ
  20. 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
  21. 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ؔ਺ͷதͰ%#ΞΫηεͳͲͯ͠ ؔ࿈ΦϒδΣΫτΛऔಘग़དྷΔ
  22. 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ͷ݁ՌΛಘΔ
  23. 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ؔ਺͕࣮ߦ͞ Εͣ%#ΞΫηε΋ൃੜ͠ͳ͍
  24. 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) }) ) )
  25. &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
  26. &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
  27. 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
  28. 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
  29. 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
  30. 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 } }
  31. w &YFDVUPSʹεΩʔϚɺ࣮ߦ͍ͨ͠ΫΤϦɺ$POUFYU ͳͲΛ༩͑Δͱ+40/Λฦͯ͘͠ΕΔ (SBQI2-ͷ࣮ߦ val result: Future[JsValue] = Executor.execute[Ctx, Unit,

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

    0QUJPOΛ͚ͭͳ͍ͱOVMMBCMFʹͳΒͳ͍ w +40/ͱͷ૬ޓม׵ w DBTFDMBTT͕͋ΔͷͰࣗಈಋग़Ͱ͖Δ w TBOHSJBͷ͓͔͛Ͱे෼ઓ͑Δ 57
  33. 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() } } }
  34. εΩʔϚ 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
  35. εΩʔϚ .VUBUJPO w શͯSPPUϨϕϧʹฒ΂Δͷ͕Φεεϝ w ωετͨ͠.VUBUJPO͸࣮ߦॱং͕อূ͞Εͳ͍ mutation { CreateTask(…) {

    id } UpdateTask(…) { id } // CreateTaskͷޙʹ࣮ߦ͞ΕΔ } mutation { Task { Create(…) { id } Update(…) { id } // Createͷޙʹ࣮ߦ͞ΕΔอূ͸ͳ͍ } } 78
  36. όʔδϣχϯά 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 }),
  37. 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
  38. w $POUFYUͷ࣮૷ྫ w ϩάΠϯϢʔβ w τϥϯβΫγϣϯ %#4FTTJPO  w &YFDVUJPO$POUFYU

    class GraphQLContext( userOpt: Option[User], session: DBSession, ec: ExecutionContext ) (SBQI2-ʹ͓͚Δ$POUFYU 94
  39. ೝূೝՄ w 8FCΞϓϦέʔγϣϯͳΒେମ͋Δ΍ͭ w ೝূ "VUIFOUJDBUJPO  w ୭͔Λಛఆ͢Δ w

    ೝՄ "VUIPSJ[BUJPO  w ݖݶΛ֬ೝ͢Δ w (SBQI2-ͱͲ͏૊Έ߹ΘͤΔ΂͖͔ɺͱ͍͏࿩ 96
  40. ೝূ 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Ͱೝূͭͭ͠ੜ੒
  41. 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
  42. ೝূ 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
  43. 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
  44. 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
  45. 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
  46. ΤϥʔϋϯυϦϯά w ͍Ζ͍Ζબ୒ࢶ͸͋Δ w શ෦औಘͰ͖ͳ͍ͱҙຯແ͍୯ҐͰΫΤϦൃߦ w গʑࣦഊͯ͠΋ϦτϥΠ͢Ε͹͑͑΍Μ w FUD w

    ੒ޭࣦഊΛಉډͤ͞Δͷ͸͔ͳΓେมͦ͏ w 6OJPO5ZQFʹͯ͠੒ޭࣦഊΛදݱ͢Δͱ͔ʁ w SFTPMWFͰྫ֎͕౤͛ΒΕͳ͍Α͏ʹέΞ͕ඞཁ 113
  47. ΤϥʔϋϯυϦϯά 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
  48. ΤϥʔϋϯυϦϯά 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
  49. ΤϥʔϋϯυϦϯά 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
  50. ΤϥʔϋϯυϦϯά w ϋϯυϦϯά͢Δ΂͖Τϥʔ͕ଟ͍ w +40/ͷTZOUBYΤϥʔ w ΫΤϦͷTZOUBYΤϥʔɺWBMJEBUJPOΤϥʔ w ϏδωεϩδοΫ্ͷΤϥʔ w

    ͦͷଞɺUISPX͞ΕͨΤϥʔ w Ͳ͏΍ͬͯϋϯυϦϯά͢Δʁ w &YDFQUJPO)BOEMFSΛؤு࣮ͬͯ૷͢Δ w &YFDVUPSFYFDVUFͷSFDPWFSͰ΋ؤுΔ 118
  51. ΤϥʔϋϯυϦϯά 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
  52. ΤϥʔϋϯυϦϯά 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
  53. / ໰୊ w / Λճආ͢ΔͨΊͷ࢓૊Έ͕ఏڙ͞Ε͍ͯΔ w %FGFSSFE3FTPMWFS w 'FUDIFS w

    SFTPMWF͢ΔͷΛ஗Βͤͯɺ·ͱΊͯऔಘͰ͖Δ΋ ͷΛ·ͱΊͯऔಘ͢ΔΑ͏ʹ͢Δ w pOE#Z*E /ΛpOE#Z*ETʹ͢Δ͚ͩͳΠϝʔδ w Ωϟογϡͨ͠Γ΋Մೳ 124
  54. 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
  55. 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>ͳॲཧ
  56. 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
  57. 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Ͱ*%Λ౉͢ͱ ͍͍ײ͡ʹ/ Λ௵ͯ͘͠ΕΔ
  58. / ໰୊ 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)) } }
  59. ·ͱΊ w (SBQI2-ศརͩ͠Φεεϝ w ۜͷ஄ؙͰ͸ͳ͍ ೋ౓໨  w ࣍ͷϓϩδΣΫτͰ΋࠾༻͍ͨ͠ w

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