Схема GraphQL с Sangria

Я смотрю библиотеку Sangria для кодирования сервера GraphQL на Scala. Однако кажется странным, что одна и та же система типов должна быть реализована дважды: (1) как часть объявлений типа GraphQL и (2) также на стороне сервера, как классы сценариев Scala, с сопутствующими ObjectType, InterfaceType и т. Д. вальс.

Жесткое кодирование системы типов в Scala особенно утомительно, поскольку моя цель - иметь возможность объединять CRUD-агрегаты произвольной формы, где каждая форма определяется как коллекция типов GraphQL. Например, предположим, что экземпляр типа Shape содержит документ GraphQL как поле; и экземпляр типа Entity имеет ссылку на его Shape, а также содержит объект Json формы, определенной в этой Shape.

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
}

(Реальный пример, конечно, также будет иметь вложенные типы и т. Д.)

Таким образом, я стремлюсь определить экземпляры типа Entity, форма которых определена в их соответствующей Shape. Я НЕ хочу жестко кодировать соответствующую sangria.schema.Schema, но хочу получить ее непосредственно из документа формы.

Есть ли готовый способ программно сгенерировать схему GraphQL из документа GraphQL, содержащего объявления типов?


person silverberry    schedule 27.04.2017    source источник
comment
Почему, например, так сложно сопоставить sangria.ast.Type с sangria.schema.OutputType?   -  person silverberry    schedule 28.04.2017


Ответы (1)


Для таких динамических сценариев использования сангрия предоставляет способ построения схемы из GraphQL IDL. Вот как вы можете это сделать (я немного упростил ваш пример, но то же самое можно реализовать, когда все эти данные поступают из отдельных классов, таких как Shape и Entity):

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 . Этот проект идет еще дальше и даже добавляет пользовательские директивы для управления генерацией функции разрешения. Код можно найти здесь:

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

person tenshi    schedule 01.05.2017
comment
Большое спасибо, тенши! Возможно, это именно то, что я ищу. - person silverberry; 01.05.2017
comment
Но есть одна проблема. Случай JsNumber должен возвращать Int, если c.field.fieldType имеет тип OutputType [Int], Long, если OutputType [Long] и т. Д. К сожалению, мы получаем только OutputType [_] со стертым TypeTag (поэтому нельзя сравнивать typeOf [T] с =: = либо). Это делает очень громоздким написание правильной функции fromJson, поскольку она должна учитывать не только ScalarLongType, но также OptionType (ScalarLongType), а также типы enum и union и т. Д. Тем не менее, это второстепенная проблема, которая может надеюсь, будет рассмотрен в будущем выпуске Сангрии. - person silverberry; 12.05.2017
comment
@silverberry информация о типе также доступна во время выполнения. Вот пример функции, которая может извлекать правильный тип scala на основе информации о типе: github.com/OlegIlyenko/graphql-toolbox/blob/master/app/ - person tenshi; 14.05.2017