SlideShare a Scribd company logo
GraphQL - when REST API is
not enough - lessons learned
Part 1
Marcin Stachniuk
8 October 2018
Marcin Stachniuk
mstachniuk.github.io
/mstachniuk/graphql-java-example
@MarcinStachniuk
wroclaw.jug.pl
shipkit.org
Agenda
11:00
● Why?
● Concepts
● Basics
12:00 Lunch
13:00
● GraphQL in Java
● Lessons learned
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Monady w .NET – i tak używasz, więc dowiedz się, o co chodzi.
Mateusz Stasch
Rewolucja w analizie danych z RevoscaleR oraz RevoscalePy
Tomasz Krawczyk
Scale it the easy way - introduction to micro frontends
Jakub Holak
Who is using REST?
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
REST - REpresentational State Transfer
https://api.example.com/customers/123
DELETE
PUT
POST
GET
PATCH
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
REST fixed 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"
}
]
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
REST consequences: several roundtrips
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
U
nderFetching
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": {
...
}
}
] } }
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
O
verFetching
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": {
...
}
}
] } }
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
REST response with projection
GET /customers/111?projection=customerPreview
{
"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": {
...
}
}
] } }
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Different clients - different needs
/web /iphone /android /tv
Application
Web iPhone Android TV
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
REST contract is rigid
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Platform Architecture
Platform
App 1 App 2
Customer
App X...
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
REST API Versioning
GET /v2/customers/111RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GET /customers/111?v=v2RE
ST
GET /customers/111 Accept header: application/vnd.myapp.2+jsonRE
ST
GET /customers/111 Custom header: x-ms-version:2RE
ST
● How to migrate?
● How to document?
● Is versionless API possible?
● What about continuous evolution of API?
Modularisation at UI
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
New Frontend Framework
ReactJS
Relay GraphQL
TypeScript
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL
● Graph Query Language
● Published by Facebook in 2015
● Growth from Facebook Graph API
● Reference implementation in JavaScript
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL main concepts
● One endpoint for all operations
● Always define in request what you need
● Queries, Mutations and Subscriptions
● Defined by schema
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Data is a graph
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL time for demo
● Fragments
● Aliases
● Directives
● Interfaces
● Unions
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
● Query
● Syntax Error
● Mutation
● Operation name
● Variables
GraphQL Simple API
GET /customers/2?fields=id,name,email
type Customer {
#fields with ! are not null
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
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL Bad Request
GET /custo!@#$ -> 404
{
"data": null,
"errors": [
{
"message": "Invalid Syntax",
"locations": [
{
"line": 2,
"column": 8
}
],
"errorType": "InvalidSyntax",
"path": null,
"extensions": null
} ] }
{
custo!@#$
}
RE
ST
http.cat/200
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Go back to the roots
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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
}
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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
}
enum Status {
NEW, CANCELED, DONE
}
{
"data": {
"customer": {
"id": "2",
"name": "name",
"orders": [
{
"id": "55",
"status": "NEW"
},
{
"id": "66",
"status": "DONE"
}
] } } }
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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 PUT /customers/123 DELETE /customers/123 PATCH /customers/123
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL mutations + operation name
mutation crCust {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
{
"data": {
"createCustomer": {
"customer": {
"id": "40",
},
"clientMutationId":
"123"
}
}
}
POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
GraphQL Variables
{
"data": {
"createCustomer": {
"customer": {
"id": "40",
},
"clientMutationId":
"123"
}
}
}
POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123
mutation crCust {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
mutation crCust ($input: CreateCustInput) {
createCustomer(input: $input) {
customer {
id
}
clientMutationId
}
}
{
"input": {
"name": "MyName 2",
"email": "me2@me.com",
"clientMutationId": "123"
}
}
GraphQL Aliases
GET /customers/2?fields=id,name,email +
{
"data": {
"cust1": {
"id": "2",
"name": "name",
"email": "a@b.com"
},
"cust2": {
"id": "3",
"name": "John Doe",
"email": "john@doe.com"
}
}
}
query get2Cust {
cust1: customer(id: "2") {
id
name
email
}
cust2: customer(id: "3") {
id
name
email
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GET /customers/3?fields=id,name,emailRE
ST
GraphQL Fragment
GET /customers/2?fields=id,name,email +
{
"data": {
"cust1": {
"id": "2",
"name": "name",
"email": "a@b.com"
},
"cust2": {
"id": "3",
"name": "John Doe",
"email": "john@doe.com"
}
}
}
query get2Cust {
cust1: customer(id: "2") {
... frag1
}
cust2: customer(id: "3") {
... frag1
}
}
fragment frag1 on Customer {
id
name
email
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GET /customers/3?fields=id,name,emailRE
ST
GraphQL Directive
GET /customers/2?fields=id,name
{
"data": {
"customer": {
"id": "2",
"name": "name"
}
}
}
query getCust ($showEmail: Boolean!) {
customer(id: "2") {
id
name
email @include(if: $showEmail)
}
}
{
"showEmail": false
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL Interface
GET /users?fields=id,name,superAdmin,permissions
type Query {
users: [User]
}
interface User {
id: ID!
name: String!
email: String!
}
type Admin implements User {
superAdmin: Boolean! // + id...
}
type Moderator implements User {
permissions: [String] // + id...
}
{
"data": {
"users": [
{
"id": "777",
"name": "Admin a",
"superAdmin": true
},
{
"id": "888",
"name": "Moderator",
"permissions": [
"Delete Customer",
"Delete comment"
]}]}}
query getUsers {
users {
... on Admin {
id
name
superAdmin
}
... on Moderator {
id
name
permissions
}
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL Union
GET /search?input=a?fields=typename,name
type Query {
search(input: String):
[SearchResult]
}
union SearchResult =
Customer |
Admin |
Moderator
{
"data": {
"search": [
{
"__typename": "Customer",
"name": "name"
},
{
"__typename": "Admin",
"name": "Admin a"
},
{
"__typename": "Moderator",
"name": "Moderator"
}]}}
query searchSmth {
search(input: "a") {
__typename
... on Customer {
name
}
... on Admin {
name
}
... on Moderator {
name
}
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Q&A
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Feedback
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
To be continued...
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL - when REST API is
not enough - lessons learned
Part 2
Marcin Stachniuk
8 October 2018
Agenda
11:00
● Why?
● Concepts
● Basics
12:00 Lunch
13:00
● GraphQL in Java
● Lessons learned
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Serverless .NET on AWS
Wojciech Gawroński
Lunch
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
https://github.com/graphql-java/graphql-java
● First usage: 21 Sep 2015
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #1
Never add a library to your project
few days after init release
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
● No community
● A lot of bugs
● Bad documentation
● Strict following reference
implementation and specification
DO NOT TRY
THIS AT WORK
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
}
enum Status {
NEW, CANCELED, DONE
}
{
"data": {
"customer": {
"id": "2",
"name": "name",
"orders": [
{
"id": "55",
"status": "NEW"
},
{
"id": "66",
"status": "DONE"
}
] } } }
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
How to implement DataFetcher for queries
GET /customers/2?fields=id,name,email,orders(id,status)
@Component
public class CustomerFetcher extends PropertyDataFetcher<Customer> {
@Autowired
private CustomerService customerService;
@Override
public Customer get(DataFetchingEnvironment environment) {
String id = environment.getArgument("id");
return customerService.getCustomerById(id);
}
}
RE
ST
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
How to implement DataFetcher for queries
GET /customers/2?fields=id,name,email,orders(id,status)
public class Customer {
private String id;
private String name;
private String email; // getters are not required
}
RE
ST
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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);
}
}
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 PUT /customers/123 DELETE /customers/123 PATCH /customers/123
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
RE
ST
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
How to implement DataFetcher for mutations
POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123
@Component
public class CreateCustomersFetcher extends
PropertyDataFetcher<CreateCustomersPayload> {
@Override
public CreateCustomerPayload get(DataFetchingEnvironment env) {
Map<String, Object> input = env.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);
}
RE
ST
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
In case Interfaces and Unions - Type Resolver is needed
public class UserTypeResolver implements TypeResolver {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
Object javaObject = env.getObject();
if (javaObject instanceof Admin) {
return env.getSchema().getObjectType("Admin");
} else if (javaObject instanceof Moderator) {
return env.getSchema().getObjectType("Moderator");
} else {
throw new RuntimeException("Unknown type " + javaObject.getClass().getName());
}
}
}
public interface User {...}
public class Admin implements User {...}
public class Moderator implements User {...}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Glue everything together
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder ->
builder.dataFetcher("customer", customerFetcher))
.type("Mutation", builder ->
builder.dataFetcher("createCustomer", createCustomerFetcher))
.type(newTypeWiring("User")
.typeResolver(new UserTypeResolver())
.build())
...
.build();
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Abstraction over GraphQL Java
Our abstraction
Data Fetcher 2
Inputs mapping to objects
Schema definition
Pagination
...
Data Fetcher 1 Data Fetcher N...
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #2
Abstraction is not good if you don’t understand
how it works under the hood
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
● Copy paste errors
● Wrong usage
● Hard to update to new version
GraphQL type system
How to define your schema?
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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 not null")
.type(new GraphQLNonNull(GraphQLID))
.build())
….
.build()))
.dataFetcher(customerFetcher)
.build();
}
Schema First approach
type Query {
customer(id: String!): Customer!
}
type Customer {
#fields with ! are not null
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Code First approach - How to build
Introspection
query
Introspection
response
Replace Relay
definitions
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Typescript relay
plugin
Schema First approach
type Customer {
# fields with ! are required
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
*.graphqls
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);
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Schema First approach - project building diagram
model.graphqls
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL SPQR
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
https://github.com/leangen/graphql-spqr
SPQR - Senatus Populusque Romanus
SPQR approach
public class UserService {
@GraphQLQuery(name = "user")
public User getById(@GraphQLArgument(name = "id") Integer id) {
//...
}
}
public class Customer {
//...
@GraphQLQuery(name = "name", description = "A person's name")
public String getName() {
return name;
}
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #3
Code First Approach is the worst
Schema First Approach:
● Easy to maintain and
understand
● Helps organise work
● Demo schema is 3x smaller
GraphQL SPQR - ?
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
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
From Code First to Schema First migration
https://github.com/mstachniuk/graphql-schema-from-introspection-generator
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL - How to define pagination, filtering, sorting?
Pagination:
● before, after
● offset, limit
Filtering:
● filter(name: “Bob” email: “%@gmail.com”)
● filter: {
OR: [{
email: “%@gmail.com”
}]
}, name: “Bob”
}
Sorting:
● orderBy: ASC, DESC
● sort: NEWEST, IMPORTANCE
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #4
GraphQL is not full query language
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
● Flexibility
● Less common conventions
● Dgraph.io created GraphQL+-
GraphQL downsides
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL downsides: N+1 problem
{
customers { 1 call
id
name
orders { n calls
id
status
}
}
}
java-dataloader
● Add async BatchLoader
● Add caching
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
graphQL = GraphQL.newGraphQL(schema)
.queryExecutionStrategy(
new BatchedExecutionStrategy())
.build();
+ @Batched in DataFetcher#get()
Lessons Learned #5
If you have N + 1 problem
use java-dataloader
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Complex Queries
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
...
}
}
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
graphQL = GraphQL.newGraphQL(schema)
.instrumentation(
new ChainedInstrumentation(asList(
new MaxQueryComplexityInstrumentation(200),
new MaxQueryDepthInstrumentation(20)
)))
.build();
Lessons Learned #6
Performance killer?
Define instrumentation!
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL downsides: Bad GraphQL API definition - examples
{
customer(id: "2") { … }
customerFull(id: "2") { … }
customerFull2(id: "2") { … }
customerWithDetails(id: "2") { … }
...
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
GraphQL downsides: Bad GraphQL API definition - examples
query getUser {
user(id: "123") {
... on Admin {
id
superAdmin
}
... on Moderator {
id
permissions
}
}
}
{
admin(id: "123") {
id
superAdmin
}
moderator(id: "123") {
id
permissions
}
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL downsides: Bad GraphQL API definition - examples
{
orders (input: {
status: "NEW"
first: "2"
offset: "3"
}, first: "1", offset: "3") {
Items { … }
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #7
Thinking shift is a key
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
● Let’s think in graphs and NOT in
endpoints / resources / entities / DTOs
● Bad design of our API
GraphQL Testing
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Testing GraphQL
@SpringBootTest
@ContextConfiguration(classes = Main)
class CustomerFetcherSpec extends Specification {
@Autowired
GraphQLSchema graphQLSchema
GraphQL graphQL
def setup() {
graphQL = GraphQL.newGraphQL(graphQLSchema).build()
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
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
}
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #8
Testing is easy
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Trap Adventure 2 - "The Hardest Retro Game"
Versioning
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL takes a strong opinion on avoiding
versioning by providing the tools for the
continuous evolution of a GraphQL schema.
Src: https://graphql.org/learn/best-practices/#versioning
Versioning
type Query {
customer(id: String!): Customer! @deprecated(reason: "not used ...")
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Lessons Learned #9
Versioning is not a problem
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GET /v2/customers/111RE
ST
GET /customers/111?v=v2RE
ST
GET /customers/111 Accept header: application/vnd.myapp.2+jsonRE
ST
GET /customers/111 Custom header: x-ms-version:2RE
ST
Versioning problem is different
Tools
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Tools GraphiQL
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Tools Relay
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
user(...) {
photo(width: "120", height: "120")
}
user(...) {
name
}
user(...) {
email
}
user(...) {
name
email
photo(width: "120", height: "120")
}
Distelli/graphql-apigen
https://github.com/Distelli/graphql-apigen
Generate Java APIs with GraphQL Schemas in order to
facilitate "schema first" development.
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL Java Tools
https://github.com/graphql-java/graphql-java-tools
Map GraphQL schema to POJOs
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
More libraries and projects related to graphql-java
https://github.com/graphql-java/awesome-graphql-java
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
● Generates a GraphQL schema from a JDBC data source
● Annotations-based syntax for GraphQL schema definition
● JPA Implementation of GraphQL (builds on graphql-java)
● And more examples
Apollo
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Build a universal GraphQL API on top of your existing REST
APIs, so you can ship new application features fast without
waiting on backend changes.
Lessons Learned #10
Tooling is nice
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
now
Summary
GraphQL Pros:
● 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
● Nice tooling
● Thin layer
GraphQL Cons:
● High entry barrier
● Hard to return simple Map
● Not well know (yet)
● Performance overhead
● A lot of similar code to write
● No Operation Idempotency
● No Cache & Security
● No Authorisation
● Always HTTP 200 -
unsupported tools
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Nothing is a silver bullet
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Q&A
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
Feedback
@MarcinStachniukGraphQL - when REST API is not enough - lessons learned
GraphQL - when REST API is
not enough - lessons learned
Part 2
Marcin Stachniuk
8 October 2018
Thank
you!

More Related Content

PDF
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
PDF
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
PDF
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
PDF
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
PDF
[DevCrowd] GraphQL - gdy API RESTowe to za mało
MarcinStachniuk
 
PDF
GraphQL - when REST API is not enough - lessons learned
MarcinStachniuk
 
PDF
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
PDF
GraphQL - Piękne API w Twojej Aplikacji - KrakowGraphAcademy
MarcinStachniuk
 
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
[DevCrowd] GraphQL - gdy API RESTowe to za mało
MarcinStachniuk
 
GraphQL - when REST API is not enough - lessons learned
MarcinStachniuk
 
GraphQL - when REST API is to less - lessons learned
MarcinStachniuk
 
GraphQL - Piękne API w Twojej Aplikacji - KrakowGraphAcademy
MarcinStachniuk
 

What's hot (11)

PDF
GraphQL - gdy API RESTowe to za mało
MarcinStachniuk
 
PDF
BruJUG Brussels GraphQL when RESR API is to less - lessons learned
MarcinStachniuk
 
PDF
API Docs Made Right / RAML - Swagger rant
Vladimir Shulyak
 
PDF
[@IndeedEng] Building Indeed Resume Search
indeedeng
 
PDF
@IndeedEng: Tokens and Millicents - technical challenges in launching Indeed...
indeedeng
 
PDF
Hypermedia APIs and HATEOAS
Vladimir Tsukur
 
PDF
Hypermedia APIs and HATEOAS / Wix Engineering
Vladimir Tsukur
 
PPT
Pascarello_Investigating JavaScript and Ajax Security
amiable_indian
 
PDF
Frank Mantek Google G Data
deimos
 
PDF
Simplify Access to Data from Pivotal GemFire Using the GraphQL (G2QL) Extension
VMware Tanzu
 
PDF
[@IndeedEng] Logrepo: Enabling Data-Driven Decisions
indeedeng
 
GraphQL - gdy API RESTowe to za mało
MarcinStachniuk
 
BruJUG Brussels GraphQL when RESR API is to less - lessons learned
MarcinStachniuk
 
API Docs Made Right / RAML - Swagger rant
Vladimir Shulyak
 
[@IndeedEng] Building Indeed Resume Search
indeedeng
 
@IndeedEng: Tokens and Millicents - technical challenges in launching Indeed...
indeedeng
 
Hypermedia APIs and HATEOAS
Vladimir Tsukur
 
Hypermedia APIs and HATEOAS / Wix Engineering
Vladimir Tsukur
 
Pascarello_Investigating JavaScript and Ajax Security
amiable_indian
 
Frank Mantek Google G Data
deimos
 
Simplify Access to Data from Pivotal GemFire Using the GraphQL (G2QL) Extension
VMware Tanzu
 
[@IndeedEng] Logrepo: Enabling Data-Driven Decisions
indeedeng
 
Ad

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

PDF
Graphql
Niv Ben David
 
PDF
apidays LIVE Paris - Augmenting a Legacy REST API with GraphQL by Clément Vil...
apidays
 
PDF
Intro to GraphQL
Rakuten Group, Inc.
 
PDF
Are you ready to adopt GraphQL?
Siva Prasad Rao Janapati
 
PDF
InterCon 2017 - Tudo o que você quer saber sobre GraphQL - Ubiratan Soares
iMasters
 
PDF
Intro to GraphQL
Charles Burgess
 
PDF
Graphql
Neven Rakonić
 
PPTX
Introduction to GraphQL
Rodrigo Prates
 
DOCX
Graphql for Frontend Developers Simplifying Data Fetching.docx
ssuser5583681
 
PDF
GraphQL Munich Meetup #1 - How We Use GraphQL At Commercetools
Nicola Molinari
 
PDF
GraphQL with .NET Core Microservices.pdf
Knoldus Inc.
 
PDF
apidays LIVE Paris - GraphQL meshes by Jens Neuse
apidays
 
DOCX
GraphQL Advanced Concepts A Comprehensive Guide.docx
ssuser5583681
 
PDF
apidays LIVE Hong Kong 2021 - GraphQL : Beyond APIs, graph your enterprise by...
apidays
 
PDF
GraphQL: The Missing Link Between Frontend and Backend Devs
Sashko Stubailo
 
PDF
Migration microservices to GraphQL
Roman Krivtsov
 
PDF
Introduction to GraphQL
Luca Galasso
 
PPTX
Taking Control of your Data with GraphQL
Vinci Rufus
 
PDF
Frontcon Riga - GraphQL Will Do To REST What JSON Did To XML
Roy Derks
 
PPTX
APIdays Paris 2019 - Delivering Exceptional User Experience with REST and Gra...
apidays
 
Graphql
Niv Ben David
 
apidays LIVE Paris - Augmenting a Legacy REST API with GraphQL by Clément Vil...
apidays
 
Intro to GraphQL
Rakuten Group, Inc.
 
Are you ready to adopt GraphQL?
Siva Prasad Rao Janapati
 
InterCon 2017 - Tudo o que você quer saber sobre GraphQL - Ubiratan Soares
iMasters
 
Intro to GraphQL
Charles Burgess
 
Introduction to GraphQL
Rodrigo Prates
 
Graphql for Frontend Developers Simplifying Data Fetching.docx
ssuser5583681
 
GraphQL Munich Meetup #1 - How We Use GraphQL At Commercetools
Nicola Molinari
 
GraphQL with .NET Core Microservices.pdf
Knoldus Inc.
 
apidays LIVE Paris - GraphQL meshes by Jens Neuse
apidays
 
GraphQL Advanced Concepts A Comprehensive Guide.docx
ssuser5583681
 
apidays LIVE Hong Kong 2021 - GraphQL : Beyond APIs, graph your enterprise by...
apidays
 
GraphQL: The Missing Link Between Frontend and Backend Devs
Sashko Stubailo
 
Migration microservices to GraphQL
Roman Krivtsov
 
Introduction to GraphQL
Luca Galasso
 
Taking Control of your Data with GraphQL
Vinci Rufus
 
Frontcon Riga - GraphQL Will Do To REST What JSON Did To XML
Roy Derks
 
APIdays Paris 2019 - Delivering Exceptional User Experience with REST and Gra...
apidays
 
Ad

More from MarcinStachniuk (20)

PDF
Wroclaw GraphQL - GraphQL in Java
MarcinStachniuk
 
PDF
[WroclawJUG] Continuous Delivery in OSS using Shipkit
MarcinStachniuk
 
PDF
Continuous Delivery in OSS using Shipkit.org
MarcinStachniuk
 
PDF
Java Web Start – jak żyć z tą dziwną technologią
MarcinStachniuk
 
PDF
Zarządzanie zmianami w schemacie relacyjnych baz danych
MarcinStachniuk
 
PDF
Inicjatywa NoSQL na przykładzie db4o
MarcinStachniuk
 
PDF
Automatic mechanism data migration between relational and object database
MarcinStachniuk
 
PDF
Zastosowanie obiektowych baz danych na przykładzie db4o
MarcinStachniuk
 
PDF
Wprowadzenie do J2ME
MarcinStachniuk
 
PDF
Continuous Delivery w projekcie Open Source - Marcin Stachniuk - DevCrowd 2017
MarcinStachniuk
 
PDF
Java Web Start czyli jak żyć z tą dziwną technologią? & Continuous Delivery w...
MarcinStachniuk
 
PDF
Java Web Start czyli jak żyć z tą dziwną technologią & Continuous Delivery w ...
MarcinStachniuk
 
PDF
Continuous delivery w projekcie open source - Marcin Stachniuk
MarcinStachniuk
 
PDF
Zarządzanie zamianami w relacyjnych bazach danych
MarcinStachniuk
 
PDF
Nowości w Javie 8 okiem programisty
MarcinStachniuk
 
PDF
Liquibase - Zarządzanie zmianami w relacyjnych bazach danych
MarcinStachniuk
 
PDF
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
MarcinStachniuk
 
PDF
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
MarcinStachniuk
 
PDF
Upieksz swoje testy! Testowanie jednostkowe dla sredniozaawansowanych.
MarcinStachniuk
 
PDF
Podstawy tworzenia gier w J2ME, dla początkujących adeptów sztuki programowania
MarcinStachniuk
 
Wroclaw GraphQL - GraphQL in Java
MarcinStachniuk
 
[WroclawJUG] Continuous Delivery in OSS using Shipkit
MarcinStachniuk
 
Continuous Delivery in OSS using Shipkit.org
MarcinStachniuk
 
Java Web Start – jak żyć z tą dziwną technologią
MarcinStachniuk
 
Zarządzanie zmianami w schemacie relacyjnych baz danych
MarcinStachniuk
 
Inicjatywa NoSQL na przykładzie db4o
MarcinStachniuk
 
Automatic mechanism data migration between relational and object database
MarcinStachniuk
 
Zastosowanie obiektowych baz danych na przykładzie db4o
MarcinStachniuk
 
Wprowadzenie do J2ME
MarcinStachniuk
 
Continuous Delivery w projekcie Open Source - Marcin Stachniuk - DevCrowd 2017
MarcinStachniuk
 
Java Web Start czyli jak żyć z tą dziwną technologią? & Continuous Delivery w...
MarcinStachniuk
 
Java Web Start czyli jak żyć z tą dziwną technologią & Continuous Delivery w ...
MarcinStachniuk
 
Continuous delivery w projekcie open source - Marcin Stachniuk
MarcinStachniuk
 
Zarządzanie zamianami w relacyjnych bazach danych
MarcinStachniuk
 
Nowości w Javie 8 okiem programisty
MarcinStachniuk
 
Liquibase - Zarządzanie zmianami w relacyjnych bazach danych
MarcinStachniuk
 
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
MarcinStachniuk
 
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
MarcinStachniuk
 
Upieksz swoje testy! Testowanie jednostkowe dla sredniozaawansowanych.
MarcinStachniuk
 
Podstawy tworzenia gier w J2ME, dla początkujących adeptów sztuki programowania
MarcinStachniuk
 

Recently uploaded (20)

PPTX
Why Use Open Source Reporting Tools for Business Intelligence.pptx
Varsha Nayak
 
PDF
Appium Automation Testing Tutorial PDF: Learn Mobile Testing in 7 Days
jamescantor38
 
PPTX
TestNG for Java Testing and Automation testing
ssuser0213cb
 
PPTX
classification of computer and basic part of digital computer
ravisinghrajpurohit3
 
PPTX
PFAS Reporting Requirements 2026 Are You Submission Ready Certivo.pptx
Certivo Inc
 
PPTX
oapresentation.pptx
mehatdhavalrajubhai
 
PDF
IEEE-CS Tech Predictions, SWEBOK and Quantum Software: Towards Q-SWEBOK
Hironori Washizaki
 
PPTX
Presentation about variables and constant.pptx
safalsingh810
 
PPTX
Contractor Management Platform and Software Solution for Compliance
SHEQ Network Limited
 
PDF
Exploring AI Agents in Process Industries
amoreira6
 
PPTX
Smart Panchayat Raj e-Governance App.pptx
Rohitnikam33
 
PDF
49784907924775488180_LRN2959_Data_Pump_23ai.pdf
Abilash868456
 
PDF
ShowUs: Pharo Stream Deck (ESUG 2025, Gdansk)
ESUG
 
PDF
QAware_Mario-Leander_Reimer_Architecting and Building a K8s-based AI Platform...
QAware GmbH
 
PDF
Build Multi-agent using Agent Development Kit
FadyIbrahim23
 
PPTX
slidesgo-unlocking-the-code-the-dynamic-dance-of-variables-and-constants-2024...
kr2589474
 
PDF
advancepresentationskillshdhdhhdhdhdhhfhf
jasmenrojas249
 
PDF
Become an Agentblazer Champion Challenge Kickoff
Dele Amefo
 
PDF
Jenkins: An open-source automation server powering CI/CD Automation
SaikatBasu37
 
PPTX
GALILEO CRS SYSTEM | GALILEO TRAVEL SOFTWARE
philipnathen82
 
Why Use Open Source Reporting Tools for Business Intelligence.pptx
Varsha Nayak
 
Appium Automation Testing Tutorial PDF: Learn Mobile Testing in 7 Days
jamescantor38
 
TestNG for Java Testing and Automation testing
ssuser0213cb
 
classification of computer and basic part of digital computer
ravisinghrajpurohit3
 
PFAS Reporting Requirements 2026 Are You Submission Ready Certivo.pptx
Certivo Inc
 
oapresentation.pptx
mehatdhavalrajubhai
 
IEEE-CS Tech Predictions, SWEBOK and Quantum Software: Towards Q-SWEBOK
Hironori Washizaki
 
Presentation about variables and constant.pptx
safalsingh810
 
Contractor Management Platform and Software Solution for Compliance
SHEQ Network Limited
 
Exploring AI Agents in Process Industries
amoreira6
 
Smart Panchayat Raj e-Governance App.pptx
Rohitnikam33
 
49784907924775488180_LRN2959_Data_Pump_23ai.pdf
Abilash868456
 
ShowUs: Pharo Stream Deck (ESUG 2025, Gdansk)
ESUG
 
QAware_Mario-Leander_Reimer_Architecting and Building a K8s-based AI Platform...
QAware GmbH
 
Build Multi-agent using Agent Development Kit
FadyIbrahim23
 
slidesgo-unlocking-the-code-the-dynamic-dance-of-variables-and-constants-2024...
kr2589474
 
advancepresentationskillshdhdhhdhdhdhhfhf
jasmenrojas249
 
Become an Agentblazer Champion Challenge Kickoff
Dele Amefo
 
Jenkins: An open-source automation server powering CI/CD Automation
SaikatBasu37
 
GALILEO CRS SYSTEM | GALILEO TRAVEL SOFTWARE
philipnathen82
 

GraphQL - when REST API is to less - lessons learned

  • 1. GraphQL - when REST API is not enough - lessons learned Part 1 Marcin Stachniuk 8 October 2018
  • 3. Agenda 11:00 ● Why? ● Concepts ● Basics 12:00 Lunch 13:00 ● GraphQL in Java ● Lessons learned @MarcinStachniukGraphQL - when REST API is not enough - lessons learned Monady w .NET – i tak używasz, więc dowiedz się, o co chodzi. Mateusz Stasch Rewolucja w analizie danych z RevoscaleR oraz RevoscalePy Tomasz Krawczyk Scale it the easy way - introduction to micro frontends Jakub Holak
  • 4. Who is using REST? @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 5. REST - REpresentational State Transfer https://api.example.com/customers/123 DELETE PUT POST GET PATCH @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 6. REST fixed response GET /customers/111 { "customer": { "id": "111", "name": "John Doe", "email": "[email protected]", "company": { "id": "222" }, "orders": [ { "id": "333" }, { "id": "444" } ] } } { "customer": { "id": "111", "name": "John Doe", "email": "[email protected]", "company": { "href": "https://api.example.com/companies/222" }, "orders": [ { "href": "https://api.example.com/orders/333" }, { "href": "https://api.example.com/orders/444" } ] } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 7. REST consequences: several roundtrips @MarcinStachniukGraphQL - when REST API is not enough - lessons learned U nderFetching
  • 8. REST response with nested data GET /customers/111 { "customer": { "id": "111", "name": "John Doe", "email": "[email protected]", "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": { ... } } ] } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned O verFetching
  • 9. 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": "[email protected]", "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": { ... } } ] } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 10. REST response with projection GET /customers/111?projection=customerPreview { "customer": { "id": "111", "name": "John Doe", "email": "[email protected]", "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": { ... } } ] } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 11. Different clients - different needs /web /iphone /android /tv Application Web iPhone Android TV RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 12. 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 RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 13. 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 RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 14. REST contract is rigid @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 15. Platform Architecture Platform App 1 App 2 Customer App X... @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 16. REST API Versioning GET /v2/customers/111RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned GET /customers/111?v=v2RE ST GET /customers/111 Accept header: application/vnd.myapp.2+jsonRE ST GET /customers/111 Custom header: x-ms-version:2RE ST ● How to migrate? ● How to document? ● Is versionless API possible? ● What about continuous evolution of API?
  • 17. Modularisation at UI @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 18. New Frontend Framework ReactJS Relay GraphQL TypeScript @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 19. GraphQL ● Graph Query Language ● Published by Facebook in 2015 ● Growth from Facebook Graph API ● Reference implementation in JavaScript @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 20. GraphQL main concepts ● One endpoint for all operations ● Always define in request what you need ● Queries, Mutations and Subscriptions ● Defined by schema @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 21. Data is a graph @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 22. GraphQL time for demo ● Fragments ● Aliases ● Directives ● Interfaces ● Unions @MarcinStachniukGraphQL - when REST API is not enough - lessons learned ● Query ● Syntax Error ● Mutation ● Operation name ● Variables
  • 23. GraphQL Simple API GET /customers/2?fields=id,name,email type Customer { #fields with ! are not null id: ID! name: String! email: String! } type Query { customer(id: String!): Customer! } { "data": { "customer": { "id": "2", "name": "name", "email": "[email protected]" } } } { customer(id: "2") { id name email } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 24. GraphQL Bad Request GET /custo!@#$ -> 404 { "data": null, "errors": [ { "message": "Invalid Syntax", "locations": [ { "line": 2, "column": 8 } ], "errorType": "InvalidSyntax", "path": null, "extensions": null } ] } { custo!@#$ } RE ST http.cat/200 @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 25. Go back to the roots @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 26. 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": "[email protected]", "company": { "id": "211", "name": "Company Corp." } } } } { customer(id: "2") { id name email company { id name } } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 27. 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 } enum Status { NEW, CANCELED, DONE } { "data": { "customer": { "id": "2", "name": "name", "orders": [ { "id": "55", "status": "NEW" }, { "id": "66", "status": "DONE" } ] } } } { customer(id: "2") { id name orders { id status } } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 28. 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 PUT /customers/123 DELETE /customers/123 PATCH /customers/123 mutation { createCustomer(input: { name: "MyName" email: "[email protected]" clientMutationId: "123" }) { customer { id } clientMutationId } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 29. GraphQL mutations + operation name mutation crCust { createCustomer(input: { name: "MyName" email: "[email protected]" clientMutationId: "123" }) { customer { id } clientMutationId } } { "data": { "createCustomer": { "customer": { "id": "40", }, "clientMutationId": "123" } } } POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned mutation { createCustomer(input: { name: "MyName" email: "[email protected]" clientMutationId: "123" }) { customer { id } clientMutationId } }
  • 30. GraphQL Variables { "data": { "createCustomer": { "customer": { "id": "40", }, "clientMutationId": "123" } } } POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123 mutation crCust { createCustomer(input: { name: "MyName" email: "[email protected]" clientMutationId: "123" }) { customer { id } clientMutationId } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned mutation crCust ($input: CreateCustInput) { createCustomer(input: $input) { customer { id } clientMutationId } } { "input": { "name": "MyName 2", "email": "[email protected]", "clientMutationId": "123" } }
  • 31. GraphQL Aliases GET /customers/2?fields=id,name,email + { "data": { "cust1": { "id": "2", "name": "name", "email": "[email protected]" }, "cust2": { "id": "3", "name": "John Doe", "email": "[email protected]" } } } query get2Cust { cust1: customer(id: "2") { id name email } cust2: customer(id: "3") { id name email } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned GET /customers/3?fields=id,name,emailRE ST
  • 32. GraphQL Fragment GET /customers/2?fields=id,name,email + { "data": { "cust1": { "id": "2", "name": "name", "email": "[email protected]" }, "cust2": { "id": "3", "name": "John Doe", "email": "[email protected]" } } } query get2Cust { cust1: customer(id: "2") { ... frag1 } cust2: customer(id: "3") { ... frag1 } } fragment frag1 on Customer { id name email } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned GET /customers/3?fields=id,name,emailRE ST
  • 33. GraphQL Directive GET /customers/2?fields=id,name { "data": { "customer": { "id": "2", "name": "name" } } } query getCust ($showEmail: Boolean!) { customer(id: "2") { id name email @include(if: $showEmail) } } { "showEmail": false } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 34. GraphQL Interface GET /users?fields=id,name,superAdmin,permissions type Query { users: [User] } interface User { id: ID! name: String! email: String! } type Admin implements User { superAdmin: Boolean! // + id... } type Moderator implements User { permissions: [String] // + id... } { "data": { "users": [ { "id": "777", "name": "Admin a", "superAdmin": true }, { "id": "888", "name": "Moderator", "permissions": [ "Delete Customer", "Delete comment" ]}]}} query getUsers { users { ... on Admin { id name superAdmin } ... on Moderator { id name permissions } } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 35. GraphQL Union GET /search?input=a?fields=typename,name type Query { search(input: String): [SearchResult] } union SearchResult = Customer | Admin | Moderator { "data": { "search": [ { "__typename": "Customer", "name": "name" }, { "__typename": "Admin", "name": "Admin a" }, { "__typename": "Moderator", "name": "Moderator" }]}} query searchSmth { search(input: "a") { __typename ... on Customer { name } ... on Admin { name } ... on Moderator { name } } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 36. Q&A @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 37. Feedback @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 38. To be continued... @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 39. GraphQL - when REST API is not enough - lessons learned Part 2 Marcin Stachniuk 8 October 2018
  • 40. Agenda 11:00 ● Why? ● Concepts ● Basics 12:00 Lunch 13:00 ● GraphQL in Java ● Lessons learned @MarcinStachniukGraphQL - when REST API is not enough - lessons learned Serverless .NET on AWS Wojciech Gawroński Lunch
  • 41. 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 https://github.com/graphql-java/graphql-java ● First usage: 21 Sep 2015 @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 42. Lessons Learned #1 Never add a library to your project few days after init release @MarcinStachniukGraphQL - when REST API is not enough - lessons learned ● No community ● A lot of bugs ● Bad documentation ● Strict following reference implementation and specification DO NOT TRY THIS AT WORK
  • 43. 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 } enum Status { NEW, CANCELED, DONE } { "data": { "customer": { "id": "2", "name": "name", "orders": [ { "id": "55", "status": "NEW" }, { "id": "66", "status": "DONE" } ] } } } { customer(id: "2") { id name orders { id status } } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 44. How to implement DataFetcher for queries GET /customers/2?fields=id,name,email,orders(id,status) @Component public class CustomerFetcher extends PropertyDataFetcher<Customer> { @Autowired private CustomerService customerService; @Override public Customer get(DataFetchingEnvironment environment) { String id = environment.getArgument("id"); return customerService.getCustomerById(id); } } RE ST { customer(id: "2") { id name orders { id status } } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 45. How to implement DataFetcher for queries GET /customers/2?fields=id,name,email,orders(id,status) public class Customer { private String id; private String name; private String email; // getters are not required } RE ST { customer(id: "2") { id name orders { id status } } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned 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); } }
  • 46. 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 PUT /customers/123 DELETE /customers/123 PATCH /customers/123 mutation { createCustomer(input: { name: "MyName" email: "[email protected]" clientMutationId: "123" }) { customer { id } clientMutationId } } RE ST @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 47. How to implement DataFetcher for mutations POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123 @Component public class CreateCustomersFetcher extends PropertyDataFetcher<CreateCustomersPayload> { @Override public CreateCustomerPayload get(DataFetchingEnvironment env) { Map<String, Object> input = env.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); } RE ST mutation { createCustomer(input: { name: "MyName" email: "[email protected]" clientMutationId: "123" }) { customer { id } clientMutationId } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 48. In case Interfaces and Unions - Type Resolver is needed public class UserTypeResolver implements TypeResolver { @Override public GraphQLObjectType getType(TypeResolutionEnvironment env) { Object javaObject = env.getObject(); if (javaObject instanceof Admin) { return env.getSchema().getObjectType("Admin"); } else if (javaObject instanceof Moderator) { return env.getSchema().getObjectType("Moderator"); } else { throw new RuntimeException("Unknown type " + javaObject.getClass().getName()); } } } public interface User {...} public class Admin implements User {...} public class Moderator implements User {...} @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 49. Glue everything together RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .type("Query", builder -> builder.dataFetcher("customer", customerFetcher)) .type("Mutation", builder -> builder.dataFetcher("createCustomer", createCustomerFetcher)) .type(newTypeWiring("User") .typeResolver(new UserTypeResolver()) .build()) ... .build(); @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 50. Abstraction over GraphQL Java Our abstraction Data Fetcher 2 Inputs mapping to objects Schema definition Pagination ... Data Fetcher 1 Data Fetcher N... @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 51. Lessons Learned #2 Abstraction is not good if you don’t understand how it works under the hood @MarcinStachniukGraphQL - when REST API is not enough - lessons learned ● Copy paste errors ● Wrong usage ● Hard to update to new version
  • 52. GraphQL type system How to define your schema? @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 53. 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 not null") .type(new GraphQLNonNull(GraphQLID)) .build()) …. .build())) .dataFetcher(customerFetcher) .build(); } Schema First approach type Query { customer(id: String!): Customer! } type Customer { #fields with ! are not null id: ID! name: String! email: String! company: Company orders: [Order] } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 54. Code First approach - How to build Introspection query Introspection response Replace Relay definitions @MarcinStachniukGraphQL - when REST API is not enough - lessons learned Typescript relay plugin
  • 55. Schema First approach type Customer { # fields with ! are required id: ID! name: String! email: String! company: Company orders: [Order] } *.graphqls 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); @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 56. Schema First approach - project building diagram model.graphqls @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 57. GraphQL SPQR @MarcinStachniukGraphQL - when REST API is not enough - lessons learned https://github.com/leangen/graphql-spqr SPQR - Senatus Populusque Romanus
  • 58. SPQR approach public class UserService { @GraphQLQuery(name = "user") public User getById(@GraphQLArgument(name = "id") Integer id) { //... } } public class Customer { //... @GraphQLQuery(name = "name", description = "A person's name") public String getName() { return name; } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 59. Lessons Learned #3 Code First Approach is the worst Schema First Approach: ● Easy to maintain and understand ● Helps organise work ● Demo schema is 3x smaller GraphQL SPQR - ? 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 @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 60. From Code First to Schema First migration https://github.com/mstachniuk/graphql-schema-from-introspection-generator @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 61. GraphQL - How to define pagination, filtering, sorting? Pagination: ● before, after ● offset, limit Filtering: ● filter(name: “Bob” email: “%@gmail.com”) ● filter: { OR: [{ email: “%@gmail.com” }] }, name: “Bob” } Sorting: ● orderBy: ASC, DESC ● sort: NEWEST, IMPORTANCE @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 62. Lessons Learned #4 GraphQL is not full query language @MarcinStachniukGraphQL - when REST API is not enough - lessons learned ● Flexibility ● Less common conventions ● Dgraph.io created GraphQL+-
  • 63. GraphQL downsides @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 64. GraphQL downsides: N+1 problem { customers { 1 call id name orders { n calls id status } } } java-dataloader ● Add async BatchLoader ● Add caching @MarcinStachniukGraphQL - when REST API is not enough - lessons learned graphQL = GraphQL.newGraphQL(schema) .queryExecutionStrategy( new BatchedExecutionStrategy()) .build(); + @Batched in DataFetcher#get()
  • 65. Lessons Learned #5 If you have N + 1 problem use java-dataloader @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 66. Complex Queries fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ... } } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned graphQL = GraphQL.newGraphQL(schema) .instrumentation( new ChainedInstrumentation(asList( new MaxQueryComplexityInstrumentation(200), new MaxQueryDepthInstrumentation(20) ))) .build();
  • 67. Lessons Learned #6 Performance killer? Define instrumentation! @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 68. GraphQL downsides: Bad GraphQL API definition - examples { customer(id: "2") { … } customerFull(id: "2") { … } customerFull2(id: "2") { … } customerWithDetails(id: "2") { … } ... } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned { customer(id: "2") { id name orders { id status } } }
  • 69. GraphQL downsides: Bad GraphQL API definition - examples query getUser { user(id: "123") { ... on Admin { id superAdmin } ... on Moderator { id permissions } } } { admin(id: "123") { id superAdmin } moderator(id: "123") { id permissions } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 70. GraphQL downsides: Bad GraphQL API definition - examples { orders (input: { status: "NEW" first: "2" offset: "3" }, first: "1", offset: "3") { Items { … } } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 71. Lessons Learned #7 Thinking shift is a key @MarcinStachniukGraphQL - when REST API is not enough - lessons learned ● Let’s think in graphs and NOT in endpoints / resources / entities / DTOs ● Bad design of our API
  • 72. GraphQL Testing @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 73. Testing GraphQL @SpringBootTest @ContextConfiguration(classes = Main) class CustomerFetcherSpec extends Specification { @Autowired GraphQLSchema graphQLSchema GraphQL graphQL def setup() { graphQL = GraphQL.newGraphQL(graphQLSchema).build() } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 74. 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 } @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 75. Lessons Learned #8 Testing is easy @MarcinStachniukGraphQL - when REST API is not enough - lessons learned Trap Adventure 2 - "The Hardest Retro Game"
  • 76. Versioning @MarcinStachniukGraphQL - when REST API is not enough - lessons learned GraphQL takes a strong opinion on avoiding versioning by providing the tools for the continuous evolution of a GraphQL schema. Src: https://graphql.org/learn/best-practices/#versioning
  • 77. Versioning type Query { customer(id: String!): Customer! @deprecated(reason: "not used ...") @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 78. Lessons Learned #9 Versioning is not a problem @MarcinStachniukGraphQL - when REST API is not enough - lessons learned GET /v2/customers/111RE ST GET /customers/111?v=v2RE ST GET /customers/111 Accept header: application/vnd.myapp.2+jsonRE ST GET /customers/111 Custom header: x-ms-version:2RE ST Versioning problem is different
  • 79. Tools @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 80. Tools GraphiQL @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 81. Tools Relay @MarcinStachniukGraphQL - when REST API is not enough - lessons learned @MarcinStachniukGraphQL - when REST API is not enough - lessons learned user(...) { photo(width: "120", height: "120") } user(...) { name } user(...) { email } user(...) { name email photo(width: "120", height: "120") }
  • 82. Distelli/graphql-apigen https://github.com/Distelli/graphql-apigen Generate Java APIs with GraphQL Schemas in order to facilitate "schema first" development. @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 83. GraphQL Java Tools https://github.com/graphql-java/graphql-java-tools Map GraphQL schema to POJOs @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 84. More libraries and projects related to graphql-java https://github.com/graphql-java/awesome-graphql-java @MarcinStachniukGraphQL - when REST API is not enough - lessons learned ● Generates a GraphQL schema from a JDBC data source ● Annotations-based syntax for GraphQL schema definition ● JPA Implementation of GraphQL (builds on graphql-java) ● And more examples
  • 85. Apollo @MarcinStachniukGraphQL - when REST API is not enough - lessons learned Build a universal GraphQL API on top of your existing REST APIs, so you can ship new application features fast without waiting on backend changes.
  • 86. Lessons Learned #10 Tooling is nice @MarcinStachniukGraphQL - when REST API is not enough - lessons learned now
  • 87. Summary GraphQL Pros: ● 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 ● Nice tooling ● Thin layer GraphQL Cons: ● High entry barrier ● Hard to return simple Map ● Not well know (yet) ● Performance overhead ● A lot of similar code to write ● No Operation Idempotency ● No Cache & Security ● No Authorisation ● Always HTTP 200 - unsupported tools @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 88. Nothing is a silver bullet @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 89. Q&A @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 90. Feedback @MarcinStachniukGraphQL - when REST API is not enough - lessons learned
  • 91. GraphQL - when REST API is not enough - lessons learned Part 2 Marcin Stachniuk 8 October 2018 Thank you!