GraphQL - when REST API is not enough - lessons learned

GraphQL - when REST API is
not enough - lessons learned
Marcin Stachniuk
Marcin Stachniuk
mstachniuk.github.io
/mstachniuk/graphql-java-example
@MarcinStachniuk
shipkit.org
wroclaw.jug.pl
collibra.com
REST - Representational state transfer
GET
POST
PUT
DELETE
PATCH
...
https://api.example.com/customers/123
REST response
GET /customers/111
{
"customer": {
"id": "111",
"name": "John Doe",
"email": "john@doe.com",
"company": {
"id": "222"
},
"orders": [
{
"id": "333"
},
{
"id": "444"
}
]
}
}
{
"customer": {
"id": "111",
"name": "John Doe",
"email": "john@doe.com",
"company": {
"href": "https://api.example.com/companies/222"
},
"orders": [
{
"href": "https://api.example.com/orders/333"
},
{
"href": "https://api.example.com/orders/444"
}
]
}
}
REST response golden rules consequences: several roundtrips
REST response with nested data
GET /customers/111
{
"customer": {
"id": "111",
"name": "John Doe",
"email": "john@doe.com",
"company": {
"id": "222",
"name": "My Awesome Corporation",
"website": "MyAwesomeCorporation.com"
},
"orders": [
{
"id": "333",
"status": "delivered",
"items": [
{
"id": "555",
"name": "Silver Bullet",
"amount": "42",
"price": "10000000",
"currency": "USD",
"producer": {
"id": "777",
"name": "Lorem Ipsum",
"website": "LoremIpsum.com"
}
}
]
},
{
"id": "444",
"name": "Golden Hammer",
"amount": "5",
"price": "10000",
"currency": "USD",
"producer": {
...
}
}
]
}
}
REST response with nested data and limit fields
GET /customers/111?fields=name,company/*,orders.status,orders.items(name,producer/name)
{
"customer": {
"id": "111",
"name": "John Doe",
"email": "john@doe.com",
"company": {
"id": "222",
"name": "My Awesome Corporation",
"website": "MyAwesomeCorporation.com"
},
"orders": [
{
"id": "333",
"status": "delivered",
"items": [
{
"id": "555",
"name": "Silver Bullet",
"amount": "42",
"price": "10000000",
"currency": "USD",
"producer": {
"id": "777",
"name": "Lorem Ipsum",
"website": "LoremIpsum.com"
}
}
]
},
{
"id": "444",
"name": "Golden Hammer",
"amount": "5",
"price": "10000",
"currency": "USD",
"producer": {
...
}
}
]
}
}
Different clients - different needs
/web /iphone /android /tv
Application
Web iPhone Android TV
Different clients - different needs
/web /iphone /android /tv
Application
Web iPhone Android TV
Content-Type: application/vnd.myawesomecorporation.com+v1+web+json
iphone
android
tv
Different clients - different needs
/web /iphone /android /tv
Application
Web iPhone Android TV
Content-Type: application/vnd.myawesomecorporation.com+v1+web+json
iphone
android
tv
Problems at Collibra
Platform
App 1 App 2
Customer
App X...
Problems at Collibra
New Frontend Framework
ReactJS
Relay GraphQL
TypeScript
GraphQL
● Graph Query Language
● Published by Facebook in 2015
● Growth from Facebook Graph API
● Reference implementation in JavaScript
● First version of Java Library: 18 Jul 2015
● First usage: 21 Sep 2015
Lessons Learned #1
Never add a library to your project
few days after init release
● No community
● A lot of bugs
● Bad documentation
● Strict following reference implementation and specification
GraphQL main concepts
● One endpoint for all operations
● Always define in request what you need
● Queries, Mutations and Subscription
● Defined by schema (type system)
● Good developer tools
GraphQL Simple API
GET /customers/2?fields=id,name,email
type Customer {
id: ID!
name: String!
email: String!
}
type Query {
customer(id: String!): Customer!
}
{
"data": {
"customer": {
"id": "2",
"name": "name",
"email": "a@b.com"
}
}
}
{
customer(id: "2") {
id
name
email
}
}
GraphQL Simple API
GET /customers/2?fields=id,name,email,company(id,name)
type Customer {
id: ID!
name: String!
email: String!
company: Company
}
type Company {
id: ID!
name: String!
website: String!
}
type Query {
customer(id: String!): Customer!
}
{
"data": {
"customer": {
"id": "2",
"name": "name",
"email": "a@b.com",
"company": {
"id": "211",
"name": "Company Corp."
}
}
}
}
{
customer(id: "2") {
id
name
email
company {
id
name
}
}
}
GraphQL Simple API
GET /customers/2?fields=id,name,email,orders(id,status)
type Customer {
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
type Order {
id: ID!
status: Status
}
type Status {
NEW, CANCELED, DONE
}
{
"data": {
"customer": {
"id": "22",
"name": "name",
"orders": [
{
"id": "55",
"status": "NEW"
},
{
"id": "66",
"status": "DONE"
}
]
} } }
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
How to implement DataFetcher for queries
GET /customers/2?fields=id,name,email,orders(id,status)
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
@Component
public class CustomerFetcher extends PropertyDataFetcher<Customer> {
@Autowired
private CustomerService customerService;
public CustomerFetcher() {
super("customer");
}
@Override
public Customer get(DataFetchingEnvironment environment) {
String id = environment.getArgument("id");
return customerService.getCustomerById(id);
}
}
How to implement DataFetcher for queries
GET /customers/2?fields=id,name,email,orders(id,status)
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
public class Customer {
private String id;
private String name;
private String email; // getters are not required
}
public class OrderDataFetcher extends PropertyDataFetcher<List<Order>> {
@Override
public List<Order> get(DataFetchingEnvironment environment) {
Customer source = environment.getSource();
String customerId = source.getId();
return orderService.getOrdersByCustomerId(customerId);
}
}
How to implement DataFetcher for queries
@Override
public Customer get(DataFetchingEnvironment environment) {
Object source = environment.getSource();
Object root = environment.getRoot();
if (source instanceof Customer) {
// ...
} else if (...) {
// ...
}
// ...
}
GraphQL mutations
input CreateCustomerInput {
name: String
email: String
clientMutationId: String!
}
type CreateCustomerPayload {
customer: Customer
clientMutationId: String!
}
type Mutation {
createCustomer(input: CreateCustomerInput):
CreateCustomerPayload!
}
{
"data": {
"createCustomer": {
"customer": {
"id": "40",
},
"clientMutationId": "123"
}
}
}
POST /customers
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
How to implement DataFetcher for mutations
POST /customers
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
@Override
public CreateCustomerPayload get(DataFetchingEnvironment environment) {
Map<String, Object> input = environment.getArgument("input");
String name = (String) input.get("name");
String email = (String) input.get("email");
String clientMutationId = (String) input.get("clientMutationId");
Customer customer = customerService.create(name, email);
return new CreateCustomerPayload(customer, clientMutationId);
}
Abstraction over GraphQL Java
Our abstraction
Data Fetcher 2
Pagination
Inputs mapping to objects
...
Data Fetcher 1 Data Fetcher N...
Lessons Learned #2
Abstraction is not good if you don’t understand
how it works under the hood
● A lot of copy paste errors
● Bad design of our API
● Let’s think in graphs and NOT in endpoints / resources / entities / DTO
GraphQL can do more!
● Aliases
● Fragments
● Interfaces
● Unions
● ...
GraphiQL
GraphiQL: github.com/graphql/graphiql
GraphQL type system
How to define your schema?
Code First approach
private GraphQLFieldDefinition customerDefinition() {
return GraphQLFieldDefinition.newFieldDefinition()
.name("customer")
.argument(GraphQLArgument.newArgument()
.name("id")
.type(new GraphQLNonNull(GraphQLString)))
.type(new GraphQLNonNull(GraphQLObjectType.newObject()
.name("Customer")
.field(GraphQLFieldDefinition.newFieldDefinition()
.name("id")
.description("fields with ! are requred")
.type(new GraphQLNonNull(GraphQLID))
.build())
….
.build()))
.dataFetcher(customerFetcher)
.build();
}
Code First approach - project building diagram
Typescript relay
plugin
Introspection
query
Introspection
response
Replace Relay
definitions
Schema First approach
type Customer {
# fields with ! are required
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
SchemaParser schemaParser = new SchemaParser();
File file = // ...
TypeDefinitionRegistry registry = schemaParser.parse(file);
SchemaGenerator schemaGenerator = new SchemaGenerator();
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder ->
builder.dataFetcher("customer", customerFetcher))
// ...
.build();
return schemaGenerator.makeExecutableSchema(registry, runtimeWiring);
*.graphqls
Code First approach - project building diagram
model.graphqls
Lessons Learned #3
Schema First Approach is better
Code First approach:
● Hard to maintain
● It was the only way at the
beginning to define a schema
● No possibility to mix both
● No easy way to migrate to
Schema First - technical debt
Schema First Approach:
● Easy to maintain and
understand
● Help organise work
● Demo schema definition only
in 54 + 23 lines, instead of 173
lines
GraphQL pagination, filtering, sorting
Pagination:
● Before, after
● Offset, limit
Filtering:
● filter: {name: “Bob” email: “%@gmail.com”}
● filter: {
OR: [{
AND: [{
releaseDate_gte: "2009"
}, {
title_starts_with: "The Dark Knight"
}]
}, name: “Bob”
}
Sorting:
● orderBy: ASC, DESC
● sort: NEWEST, IMPORTANCE
Lessons Learned #4
GraphQL is not full query language
● More flexibility
● Less common conventions
● Dgraph.io created GraphQL+- extension with filtering, regular
expressions, i18n, equal/greater/less…
Assumptions enforced by Relay
● All returned types need to implement
Node interface
interface Node {
id: ID!
}● All IDs needs to be unique in whole
graph
● ID should be base64
● For pagination use PageInfo
type PageInfo {
hasNextPage: Boolean
hasPreviousPage: Boolean
# optional:
startCursor: String
endCursor: String
}
Relay mutations namings conventions
input CreateCustomerInput {
name: String
email: String
clientMutationId: String!
}
type CreateCustomerPayload {
customer: Customer
clientMutationId: String!
}
type Mutation {
createCustomer(input: CreateCustomerInput): CreateCustomerPayload!
}
N+1 problem
{
customers { 1 call
id
name
orders { n calls
id
status
}
}
}
java-dataloader
● Add async BatchLoader
● Add caching
Lessons Learned #5
If you have N + 1 problem
use java-dataloader
Testing GraphQL
@SpringBootTest
@ContextConfiguration(classes = Main)
class CustomerFetcherSpec extends Specification {
@Autowired
GraphQLSchema graphQLSchema
GraphQL graphQL
def setup() {
graphQL = GraphQL.newGraphQL(graphQLSchema).build()
}
Testing GraphQL
def "should get customer by id"() {
given:
def query = """{ customer(id: "2") { … } }"""
def expected = [ "customer": [ … ] ]
when:
def result = graphQL.execute(query)
then:
result.data == expected
}
Summary
GraphQL:
● Nice alternative to REST
● It can be used together with REST
● Good integration with Relay / ReactJS
● You get exactly what you want to get
● Good for API with different clients
● Good to use on top of existing API
● Self documented
● Easy testing
Nothing is a silver bullet
GraphQL - when REST API is
not enough - lessons learned
Marcin Stachniuk
Thankyou!
1 of 45

More Related Content

What's hot(19)

Nativescript angularNativescript angular
Nativescript angular
Christoffer Noring1K views
Html5 Interview Questions & AnswersHtml5 Interview Questions & Answers
Html5 Interview Questions & Answers
Ratnala Charan kumar329 views
Wcf data servicesWcf data services
Wcf data services
Eyal Vardi2.5K views
Grails Custom Tag libGrails Custom Tag lib
Grails Custom Tag lib
Ali Tanwir1.1K views
JavascriptJavascript
Javascript
Rajavel Dhandabani1.7K views
Java script -23jan2015Java script -23jan2015
Java script -23jan2015
Sasidhar Kothuru1.5K views
Introduction to AJAX and DWRIntroduction to AJAX and DWR
Introduction to AJAX and DWR
SweNz FixEd3.3K views
ajax_pdfajax_pdf
ajax_pdf
tutorialsruby540 views
Evolving The Java LanguageEvolving The Java Language
Evolving The Java Language
QConLondon2008424 views
Enter the gradleEnter the gradle
Enter the gradle
Parameswari Ettiappan305 views
Angular JS2 Training Session #1Angular JS2 Training Session #1
Angular JS2 Training Session #1
Paras Mendiratta95 views
Grails   InternationalizationGrails   Internationalization
Grails Internationalization
nakul-pant804 views

Similar to GraphQL - when REST API is not enough - lessons learned(20)

Scalable web application architectureScalable web application architecture
Scalable web application architecture
postrational32.5K views
GraphQL - gdy API RESTowe to za małoGraphQL - gdy API RESTowe to za mało
GraphQL - gdy API RESTowe to za mało
MarcinStachniuk228 views
Wroclaw GraphQL - GraphQL in JavaWroclaw GraphQL - GraphQL in Java
Wroclaw GraphQL - GraphQL in Java
MarcinStachniuk1.1K views
VBA API for scriptDB primerVBA API for scriptDB primer
VBA API for scriptDB primer
Bruce McPherson6.8K views

More from MarcinStachniuk(19)

Inicjatywa NoSQL na przykładzie db4oInicjatywa NoSQL na przykładzie db4o
Inicjatywa NoSQL na przykładzie db4o
MarcinStachniuk2.1K views
Wprowadzenie do J2MEWprowadzenie do J2ME
Wprowadzenie do J2ME
MarcinStachniuk65 views

Recently uploaded(20)

ChatGPT and AI for Web DevelopersChatGPT and AI for Web Developers
ChatGPT and AI for Web Developers
Maximiliano Firtman161 views
METHOD AND SYSTEM FOR PREDICTING OPTIMAL LOAD FOR WHICH THE YIELD IS MAXIMUM ...METHOD AND SYSTEM FOR PREDICTING OPTIMAL LOAD FOR WHICH THE YIELD IS MAXIMUM ...
METHOD AND SYSTEM FOR PREDICTING OPTIMAL LOAD FOR WHICH THE YIELD IS MAXIMUM ...
Prity Khastgir IPR Strategic India Patent Attorney Amplify Innovation24 views
Web Dev - 1 PPT.pdfWeb Dev - 1 PPT.pdf
Web Dev - 1 PPT.pdf
gdsczhcet49 views
ThroughputThroughput
Throughput
Moisés Armani Ramírez31 views
Java Platform Approach 1.0 - Picnic MeetupJava Platform Approach 1.0 - Picnic Meetup
Java Platform Approach 1.0 - Picnic Meetup
Rick Ossendrijver24 views
Liqid: Composable CXL PreviewLiqid: Composable CXL Preview
Liqid: Composable CXL Preview
CXL Forum120 views
The Research Portal of Catalonia: Growing more (information) & more (services)The Research Portal of Catalonia: Growing more (information) & more (services)
The Research Portal of Catalonia: Growing more (information) & more (services)
CSUC - Consorci de Serveis Universitaris de Catalunya59 views
[2023] Putting the R! in R&D.pdf[2023] Putting the R! in R&D.pdf
[2023] Putting the R! in R&D.pdf
Eleanor McHugh36 views

GraphQL - when REST API is not enough - lessons learned

  • 1. GraphQL - when REST API is not enough - lessons learned Marcin Stachniuk
  • 3. REST - Representational state transfer GET POST PUT DELETE PATCH ... https://api.example.com/customers/123
  • 4. REST response GET /customers/111 { "customer": { "id": "111", "name": "John Doe", "email": "john@doe.com", "company": { "id": "222" }, "orders": [ { "id": "333" }, { "id": "444" } ] } } { "customer": { "id": "111", "name": "John Doe", "email": "john@doe.com", "company": { "href": "https://api.example.com/companies/222" }, "orders": [ { "href": "https://api.example.com/orders/333" }, { "href": "https://api.example.com/orders/444" } ] } }
  • 5. REST response golden rules consequences: several roundtrips
  • 6. REST response with nested data GET /customers/111 { "customer": { "id": "111", "name": "John Doe", "email": "john@doe.com", "company": { "id": "222", "name": "My Awesome Corporation", "website": "MyAwesomeCorporation.com" }, "orders": [ { "id": "333", "status": "delivered", "items": [ { "id": "555", "name": "Silver Bullet", "amount": "42", "price": "10000000", "currency": "USD", "producer": { "id": "777", "name": "Lorem Ipsum", "website": "LoremIpsum.com" } } ] }, { "id": "444", "name": "Golden Hammer", "amount": "5", "price": "10000", "currency": "USD", "producer": { ... } } ] } }
  • 7. REST response with nested data and limit fields GET /customers/111?fields=name,company/*,orders.status,orders.items(name,producer/name) { "customer": { "id": "111", "name": "John Doe", "email": "john@doe.com", "company": { "id": "222", "name": "My Awesome Corporation", "website": "MyAwesomeCorporation.com" }, "orders": [ { "id": "333", "status": "delivered", "items": [ { "id": "555", "name": "Silver Bullet", "amount": "42", "price": "10000000", "currency": "USD", "producer": { "id": "777", "name": "Lorem Ipsum", "website": "LoremIpsum.com" } } ] }, { "id": "444", "name": "Golden Hammer", "amount": "5", "price": "10000", "currency": "USD", "producer": { ... } } ] } }
  • 8. Different clients - different needs /web /iphone /android /tv Application Web iPhone Android TV
  • 9. Different clients - different needs /web /iphone /android /tv Application Web iPhone Android TV Content-Type: application/vnd.myawesomecorporation.com+v1+web+json iphone android tv
  • 10. Different clients - different needs /web /iphone /android /tv Application Web iPhone Android TV Content-Type: application/vnd.myawesomecorporation.com+v1+web+json iphone android tv
  • 11. Problems at Collibra Platform App 1 App 2 Customer App X...
  • 14. GraphQL ● Graph Query Language ● Published by Facebook in 2015 ● Growth from Facebook Graph API ● Reference implementation in JavaScript ● First version of Java Library: 18 Jul 2015 ● First usage: 21 Sep 2015
  • 15. Lessons Learned #1 Never add a library to your project few days after init release ● No community ● A lot of bugs ● Bad documentation ● Strict following reference implementation and specification
  • 16. GraphQL main concepts ● One endpoint for all operations ● Always define in request what you need ● Queries, Mutations and Subscription ● Defined by schema (type system) ● Good developer tools
  • 17. GraphQL Simple API GET /customers/2?fields=id,name,email type Customer { id: ID! name: String! email: String! } type Query { customer(id: String!): Customer! } { "data": { "customer": { "id": "2", "name": "name", "email": "a@b.com" } } } { customer(id: "2") { id name email } }
  • 18. GraphQL Simple API GET /customers/2?fields=id,name,email,company(id,name) type Customer { id: ID! name: String! email: String! company: Company } type Company { id: ID! name: String! website: String! } type Query { customer(id: String!): Customer! } { "data": { "customer": { "id": "2", "name": "name", "email": "a@b.com", "company": { "id": "211", "name": "Company Corp." } } } } { customer(id: "2") { id name email company { id name } } }
  • 19. GraphQL Simple API GET /customers/2?fields=id,name,email,orders(id,status) type Customer { id: ID! name: String! email: String! company: Company orders: [Order] } type Order { id: ID! status: Status } type Status { NEW, CANCELED, DONE } { "data": { "customer": { "id": "22", "name": "name", "orders": [ { "id": "55", "status": "NEW" }, { "id": "66", "status": "DONE" } ] } } } { customer(id: "2") { id name orders { id status } } }
  • 20. How to implement DataFetcher for queries GET /customers/2?fields=id,name,email,orders(id,status) { customer(id: "2") { id name orders { id status } } } @Component public class CustomerFetcher extends PropertyDataFetcher<Customer> { @Autowired private CustomerService customerService; public CustomerFetcher() { super("customer"); } @Override public Customer get(DataFetchingEnvironment environment) { String id = environment.getArgument("id"); return customerService.getCustomerById(id); } }
  • 21. How to implement DataFetcher for queries GET /customers/2?fields=id,name,email,orders(id,status) { customer(id: "2") { id name orders { id status } } } public class Customer { private String id; private String name; private String email; // getters are not required } public class OrderDataFetcher extends PropertyDataFetcher<List<Order>> { @Override public List<Order> get(DataFetchingEnvironment environment) { Customer source = environment.getSource(); String customerId = source.getId(); return orderService.getOrdersByCustomerId(customerId); } }
  • 22. How to implement DataFetcher for queries @Override public Customer get(DataFetchingEnvironment environment) { Object source = environment.getSource(); Object root = environment.getRoot(); if (source instanceof Customer) { // ... } else if (...) { // ... } // ... }
  • 23. GraphQL mutations input CreateCustomerInput { name: String email: String clientMutationId: String! } type CreateCustomerPayload { customer: Customer clientMutationId: String! } type Mutation { createCustomer(input: CreateCustomerInput): CreateCustomerPayload! } { "data": { "createCustomer": { "customer": { "id": "40", }, "clientMutationId": "123" } } } POST /customers mutation { createCustomer(input: { name: "MyName" email: "me@me.com" clientMutationId: "123" }) { customer { id } clientMutationId } }
  • 24. How to implement DataFetcher for mutations POST /customers mutation { createCustomer(input: { name: "MyName" email: "me@me.com" clientMutationId: "123" }) { customer { id } clientMutationId } } @Override public CreateCustomerPayload get(DataFetchingEnvironment environment) { Map<String, Object> input = environment.getArgument("input"); String name = (String) input.get("name"); String email = (String) input.get("email"); String clientMutationId = (String) input.get("clientMutationId"); Customer customer = customerService.create(name, email); return new CreateCustomerPayload(customer, clientMutationId); }
  • 25. Abstraction over GraphQL Java Our abstraction Data Fetcher 2 Pagination Inputs mapping to objects ... Data Fetcher 1 Data Fetcher N...
  • 26. Lessons Learned #2 Abstraction is not good if you don’t understand how it works under the hood ● A lot of copy paste errors ● Bad design of our API ● Let’s think in graphs and NOT in endpoints / resources / entities / DTO
  • 27. GraphQL can do more! ● Aliases ● Fragments ● Interfaces ● Unions ● ...
  • 29. GraphQL type system How to define your schema?
  • 30. Code First approach private GraphQLFieldDefinition customerDefinition() { return GraphQLFieldDefinition.newFieldDefinition() .name("customer") .argument(GraphQLArgument.newArgument() .name("id") .type(new GraphQLNonNull(GraphQLString))) .type(new GraphQLNonNull(GraphQLObjectType.newObject() .name("Customer") .field(GraphQLFieldDefinition.newFieldDefinition() .name("id") .description("fields with ! are requred") .type(new GraphQLNonNull(GraphQLID)) .build()) …. .build())) .dataFetcher(customerFetcher) .build(); }
  • 31. Code First approach - project building diagram Typescript relay plugin Introspection query Introspection response Replace Relay definitions
  • 32. Schema First approach type Customer { # fields with ! are required id: ID! name: String! email: String! company: Company orders: [Order] } SchemaParser schemaParser = new SchemaParser(); File file = // ... TypeDefinitionRegistry registry = schemaParser.parse(file); SchemaGenerator schemaGenerator = new SchemaGenerator(); RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .type("Query", builder -> builder.dataFetcher("customer", customerFetcher)) // ... .build(); return schemaGenerator.makeExecutableSchema(registry, runtimeWiring); *.graphqls
  • 33. Code First approach - project building diagram model.graphqls
  • 34. Lessons Learned #3 Schema First Approach is better Code First approach: ● Hard to maintain ● It was the only way at the beginning to define a schema ● No possibility to mix both ● No easy way to migrate to Schema First - technical debt Schema First Approach: ● Easy to maintain and understand ● Help organise work ● Demo schema definition only in 54 + 23 lines, instead of 173 lines
  • 35. GraphQL pagination, filtering, sorting Pagination: ● Before, after ● Offset, limit Filtering: ● filter: {name: “Bob” email: “%@gmail.com”} ● filter: { OR: [{ AND: [{ releaseDate_gte: "2009" }, { title_starts_with: "The Dark Knight" }] }, name: “Bob” } Sorting: ● orderBy: ASC, DESC ● sort: NEWEST, IMPORTANCE
  • 36. Lessons Learned #4 GraphQL is not full query language ● More flexibility ● Less common conventions ● Dgraph.io created GraphQL+- extension with filtering, regular expressions, i18n, equal/greater/less…
  • 37. Assumptions enforced by Relay ● All returned types need to implement Node interface interface Node { id: ID! }● All IDs needs to be unique in whole graph ● ID should be base64 ● For pagination use PageInfo type PageInfo { hasNextPage: Boolean hasPreviousPage: Boolean # optional: startCursor: String endCursor: String }
  • 38. Relay mutations namings conventions input CreateCustomerInput { name: String email: String clientMutationId: String! } type CreateCustomerPayload { customer: Customer clientMutationId: String! } type Mutation { createCustomer(input: CreateCustomerInput): CreateCustomerPayload! }
  • 39. N+1 problem { customers { 1 call id name orders { n calls id status } } } java-dataloader ● Add async BatchLoader ● Add caching
  • 40. Lessons Learned #5 If you have N + 1 problem use java-dataloader
  • 41. Testing GraphQL @SpringBootTest @ContextConfiguration(classes = Main) class CustomerFetcherSpec extends Specification { @Autowired GraphQLSchema graphQLSchema GraphQL graphQL def setup() { graphQL = GraphQL.newGraphQL(graphQLSchema).build() }
  • 42. Testing GraphQL def "should get customer by id"() { given: def query = """{ customer(id: "2") { … } }""" def expected = [ "customer": [ … ] ] when: def result = graphQL.execute(query) then: result.data == expected }
  • 43. Summary GraphQL: ● Nice alternative to REST ● It can be used together with REST ● Good integration with Relay / ReactJS ● You get exactly what you want to get ● Good for API with different clients ● Good to use on top of existing API ● Self documented ● Easy testing
  • 44. Nothing is a silver bullet
  • 45. GraphQL - when REST API is not enough - lessons learned Marcin Stachniuk Thankyou!