Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQL Schema with Sangria

I'm looking at the Sangria library for coding a GraphQL server in Scala. It feels odd, however, that the same type system must be implemented twice: (1) as part of the GraphQL type declarations, and (2) also at the server side, as Scala case classes, with accompanying ObjectType, InterfaceType, etc. vals.

Hardcoding the type system in Scala is especially irksome, since my purpose is to be able to CRUD aggregates of arbitrary shape, where each shape is defined as a GraphQL collection of types. For example, say an instance of type Shape contains a GraphQL document as a field; and an instance of type Entity has a reference to its Shape and also contains a Json object of the shape defined in that Shape.

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

For example, if the shape document is something like this:

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

then the Json content in the entity could be something like this:

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

(A real example would, of course, also have nested types, etc.)

Thus, I seek to be able to define instances of type Entity whose shape is defined in their corresponding Shape. I do NOT want to hardcode the corresponding sangria.schema.Schema but want to derive it directly from the shape document.

Is there a ready way to generate a GraphQL schema programmatically from a GraphQL document containing type declarations?

like image 469
silverberry Avatar asked Apr 27 '17 18:04

silverberry


People also ask

Can GraphQL schema have multiple queries?

When doing query batching, the GraphQL server executes multiple queries in a single request. But those queries are still independent from each other. They just happen to be executed one after the other, to avoid the latency from multiple requests.

What other types can be used in a GraphQL schema?

The GraphQL schema language supports the scalar types of String , Int , Float , Boolean , and ID , so you can use these directly in the schema you pass to buildSchema . By default, every type is nullable - it's legitimate to return null as any of the scalar types.


1 Answers

For such dynamic use-cases, sangria provides a way to build a schema from GraphQL IDL. Here is how you can do it (I simplified your example a bit, but the same can be implemented when all this data comes from separate classes like Shape and 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)

In order to define how resolve functions should be generated, you need to create a custom schema builder, like this one, and just override resolveField method:

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
    }
  }

When you execute this example, you will see following JSON result:

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

If you would like to see a more complex example, I would recommend you to check out GrapohQL Toolbox "proxy". This project takes it one step further and even adds custom directives to control the resolve function generation. The code can be found here:

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

like image 136
tenshi Avatar answered Sep 28 '22 01:09

tenshi