GraphQL - a type system for your API

by Oleg Ilyenko / @easyangel

A glimpse into the Future

Wouldn't it be great if...


          

Typical Rest API

Common Issues

  • Over-fetching
    • /products?field=name&
      /products?field=description&
      /products?field=variants[*].price
  • Under-fetching
    • /products?expand=productType&
      /products?expand=variants[*].price.taxRate
  • API changes and evolution
    • Versioning
    • Deprecation
    • Maintenance

API Gateways

a.k.a. BFF (Backend For a Frontend)

GraphQL Approach

A Data Query Language

GraphQL

  • Developed by Facebook
  • Used internally since 2012
  • Open source version is published in July 2015
  • 15+ programming languages
  • Specification: https://facebook.github.io/graphql

Response Structure


{
  "data": {
    "product": {
      "name": "Delicious Cake",
      "description": "Just taste it!"

      "picture": {
        "width": 150,
        "height": 150,
        "url": "http://..."
      }
    }
  }
}
          

Response Structure


{

     product   {
       name
       description

       picture   {
         width
         height
         url
      }
    }

}
          

Every Field Is a Function


query MyProduct {
  products {
    picture(size: 300) {
      width, height, url
    }
  }
}
          

{
  "data": {
    "products": [
      {
        "picture": {
          "width": 300,
          "height": 300,
          "url": "http://..."
        }
      },
      ...
    ]
  }
}
          

Aliases


query MyProduct {
  products {
    thumb: picture(size: 100) {
      width
    }
    fullSize: picture(size: 500) {
      width
    }
  }
}
          

{
  "data": {
    "products": [
      {
        "thumb": {
          "width": 100
        },
        "fullSize": {
          "width": 500
        }
      },
      ...
    ]
  }
}
          

GraphQL is NOT

  • Database API
    • Not SQL
    • Does not require specific storage
  • Serialization format
    • works with JSON and similar formats
  • Transport protocol
    • Most often used with HTTP
    • Can be used with TCP, UDP, etc.

-agnostic

A Scala GraphQL Implementation

Type System


type Picture {
  width: Int!
  height: Int!
  url: String
}
          

case class Picture(
  width: Int,
  height: Int,
  url: Option[String]
)
          

val PictureType = ObjectType(
  "Picture",
  "The product picture",

  fields[Unit, Picture](
    Field("width", IntType,
      resolve = _.value.width),

    Field("height", IntType,
      resolve = _.value.height),

    Field("url", OptionType(StringType),
      description = Some("Picture CDN URL"),
      resolve = _.value.url)))
          

Type Derivation


type Picture {
  width: Int!
  height: Int!
  url: String
}
          

case class Picture(
  width: Int,
  height: Int,
  url: Option[String]
)
          

val PictureType =
  deriveObjectType[Unit, Picture](
    ObjectTypeDescription("The product picture"),
    DocumentField("url", "Picture CDN URL"))
          

Interfaces


interface Identifiable {
  id: String!
}
          

trait Identifiable {
  def id: String
}
          

val IdentifiableType = InterfaceType(
  "Identifiable",
  "Entity that can be identified",

  fields[Unit, Identifiable](
    Field("id", StringType,
      resolve = _.value.id)))
          

Arguments


type Product
    implements Identifiable {
  id: String!
  name: String!
  description: String
  picture(size: Int!): Picture
}
          

case class Product(
  id: String,
  name: String,
  description: String
) extends Identifiable {
  def picture(size: Int): Picture =
    Picture(
      width = size,
      height = size,
      url = Some(
        s"//cdn.com/$size/$id.jpg"))
}

          

val ProductType =
  deriveObjectType[Unit, Product](
    Interfaces(IdentifiableType),
    IncludeMethods("picture"))
          

Query Type


class ProductRepo {
  def products: List[Product] =
    Product("1", "Cheesecake", "Tasty") ::
    Product("2", "Health Potion", "+50 HP") :: Nil

  def product(id: String): Option[Product] =
    products find (_.id == id)
}
          

val IdArg = Argument("id", StringType)

val QueryType = ObjectType("Query", "Query entry point",
  fields[ProductRepo, Unit](
    Field("product", OptionType(ProductType),
      description = Some("Get product by ID"),
      arguments = IdArg :: Nil,
      resolve = c ⇒ c.ctx.product(c arg IdArg)),

    Field("products", ListType(ProductType),
      description = Some("List all product"),
      resolve = c ⇒ c.ctx.products)))
          

Query Execution


val query =
  graphql"""
    query MyProduct {
      product(id: "2") {
        name
        description

        picture(size: 500) {
          width, height, url
        }
      }
    }
  """
          

val schema = Schema(QueryType)

val result: Future[Json] =
  Executor.execute(schema, query, new ProductRepo)
          

Features

  • Marshalling
    • spray-json, circe, MessagePack, Amazon Ion, ...
  • Stream-based subscriptions
    • akka-streams, monix, RxScala, ...
  • Deferred values/Fetch API
    • Helps to solve N+1 query problem
  • Middleware
    • Query execution instrumentation
  • Query reducers
    • Query analysis before execution
    • Query complexity measurement

Sangria Users

https://developer.github.com/v4

Thank you!




Questions?