GraphQL - a type system for your API

by Oleg Ilyenko / @easyangel

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
  • Completely backend agnostic
  • Specification: https://facebook.github.io/graphql

Response Structure


query MyProduct {
  product(id: 123) {
    name
    description

    picture {
      width
      height
      url
    }
  }
}
          

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

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

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
        }
      },
      ...
    ]
  }
}
          

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


type Query {
  product(id: Int!): Product
  products: [Product]
}
          

class ProductRepo {
  val Products = List(
    Product("1", "Cheesecake", "Tasty"),
    Product("2", "Health Potion", "+50 HP"))

  @GraphQLField
  def product(id: String) =
    Products find (_.id == id)
}

case class Ctx(repo: ProductRepo)
          

val QueryType =
  deriveContextObjectType[Ctx, ProductRepo, Unit](
    _.repo, ObjectTypeName("Query"))
          

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, Ctx(new ProductRepo))
          

Demo Time


{
  hero {
    id
    name
  }
}

// Response
{
  "data": {
    "hero": {
      "id": "2001",
      "name": "R2-D2"
    }
  }
}
          

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

Thank you!




Questions?