【问题标题】:GraphQL Schema with Sangria带有 Sangria 的 GraphQL Schema
【发布时间】:2017-09-25 17:31:27
【问题描述】:

我正在查看用于在 Scala 中编写 GraphQL 服务器的 Sangria 库。然而,奇怪的是,同一个类型系统必须实现两次:(1)作为 GraphQL 类型声明的一部分,(2)也在服务器端,作为 Scala 案例类,伴随着 ObjectType、InterfaceType 等。值。

在 Scala 中对类型系统进行硬编码尤其令人讨厌,因为我的目的是能够对任意形状的聚合进行 CRUD,其中每个形状都定义为 GraphQL 类型集合。例如,假设 Shape 类型的实例包含一个 GraphQL 文档作为字段;并且 Entity 类型的实例具有对其 S​​hape 的引用,并且还包含该 Shape 中定义的形状的 Json 对象。

case class Shape(id: String, name: String, doc: sangria.ast.Document)
case class Entity(id: String, name: String, shape: Shape, content: JsValue)

例如,如果形状文档是这样的:

type Person {
  firstName: String!
  lastName: String!
  age: Int
}

那么实体中的 Json 内容可能是这样的:

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 30
}

(一个真实的例子当然也有嵌套类型等)

因此,我寻求能够定义实体类型的实例,其形状在其相应的形状中定义。我不想硬编码相应的 sangria.schema.Schema,但想直接从形状文档中派生它。

是否有现成的方法可以从包含类型声明的 GraphQL 文档中以编程方式生成 GraphQL 架构?

【问题讨论】:

  • 为什么如此痛苦,例如,将 sangria.ast.Type 映射到 sangria.schema.OutputType?

标签: json scala graphql sangria


【解决方案1】:

对于此类动态用例,sangria 提供了一种从 GraphQL IDL 构建模式的方法。这是你可以做到的(我稍微简化了你的例子,但是当所有这些数据来自不同的类,如ShapeEntity时,同样可以实现):

import sangria.ast._
import sangria.schema._
import sangria.macros._
import sangria.marshalling.sprayJson._
import sangria.execution.Executor

import scala.concurrent.ExecutionContext.Implicits.global
import spray.json._

val schemaAst =
  gql"""
    type Person {
      firstName: String!
      lastName: String!
      age: Int
    }

    type Query {
      people: [Person!]
    }
  """

val schema = Schema.buildFromAst(schemaAst, builder)

val query =
  gql"""
    {
      people {
        firstName
        age
      }
    }
  """

val data =
  """
    {
      "people": [{
        "firstName": "John",
        "lastName": "Smith",
        "age": 30
      }]
    }
  """.parseJson

val result = Executor.execute(schema, query, data)

为了定义应该如何生成resolve 函数,您需要创建一个自定义模式构建器,就像这个,然后覆盖resolveField 方法:

val builder =
  new DefaultAstSchemaBuilder[JsValue] {
    override def resolveField(typeDefinition: TypeDefinition, definition: FieldDefinition) =
      typeDefinition.name match {
        case "Query" ⇒
          c ⇒ c.ctx.asJsObject.fields get c.field.name map fromJson
        case _ ⇒
          c ⇒ fromJson(c.value.asInstanceOf[JsObject].fields(c.field.name))
      }

    def fromJson(v: JsValue) = v match {
      case JsArray(l) ⇒ l
      case JsString(s) ⇒ s
      case JsNumber(n) ⇒ n.intValue()
      case other ⇒ other
    }
  }

执行此示例时,您将看到以下 JSON result

{
  "data": {
    "people": [{
      "firstName": "John",
      "age": 30
    }]
  }
}

如果您想查看更复杂的示例,我建议您查看GrapohQL Toolbox "proxy"。这个项目更进一步,甚至添加了自定义指令来控制解析函数的生成。代码可以在这里找到:

https://github.com/OlegIlyenko/graphql-toolbox

【讨论】:

  • 非常感谢,天师!这可能正是我想要的。
  • 一个问题,不过。如果 c.field.fieldType 是 OutputType[Int] 类型,则 JsNumber 的情况应返回 Int,如果 OutputType[Long] 等,则返回 Long。不幸的是,我们只得到 OutputType[_],TypeTag 已擦除(因此无法比较 typeOf [T] 与 =:= 或者)。这使得编写正确的 fromJson 函数变得非常麻烦,因为它不仅必须考虑 ScalarLongType,还必须考虑 OptionType(ScalarLongType),以及枚举和联合类型等。不过,这是一个小问题,可能希望在未来版本的桑格利亚汽酒中得到解决。
  • @silverberry 类型信息在运行时也可用。这是一个可以根据类型信息提取正确 scala 类型的函数示例:github.com/OlegIlyenko/graphql-toolbox/blob/master/app/…
猜你喜欢
  • 2018-08-09
  • 2017-06-10
  • 1970-01-01
  • 2018-01-11
  • 2017-06-24
  • 2017-06-07
  • 2018-09-07
  • 2018-12-31
  • 2018-06-06
相关资源
最近更新 更多