Do REST ao GraphQL
com PHP
BRUNO NEVES
brunonm@gmail.com
@brunodasneves
Roteiro
REST & GraphQL
História
Especificação / Linguagem
PHP
Newsfeed App
GET /news/1
{
"id": "1",
"user_id": "57",
"media_id": "20",
"title": "Joesley rompe com Tony Ramos",
"excerpt": "Mussum, Ipsum, cacilds",
"text": "Mussum, Ipsum, cacilds vidis litro abertis...",
"created_at": "1495928417",
"status": "PUBLISHED"
}
GET /users/57
{
"id": "57",
"name": "Mad Max",
"email": "madmax@locaweb.com",
"role": "WRITER",
"created_at": "1495928417"
}
GET /medias/20
{
"id": "20",
"type": "image/jpeg",
"filename": "friboi.jpg",
"size": "204800",
"url": "http://news/media/65815736d29cc094a2d784c338066029"
}
GET /news/1/comments
[
{
"id": "25",
"news_id": "1",
"text": "Não sou faixa preta cumpadi, sou preto inteiris.",
"created_at": "1495928417",
"author": {
"name": "Nissim Ourfali",
"email": "nissimourfali@locaweb.com"
}
}, //... a lot of comments
]
/news/1
/users/57
//...
/medias/20
/news/1/comments
Como seria pedir
SOMENTE
o necessário e receber
EXATAMENTE
o que pediu de
UMA SÓ VEZ?
{
news(id: 1) {
title, text, created_at,
media {
url
},
user {
name
},
comments {
text,
author {
name
}
}
}
}
{
news(id: 1) {
title, text, created_at,
media {
url
},
user {
name
},
comments {
text,
author {
name
}
}
}
}
[
{
"title": "Joesley rompe com Tony Ramos",
"text": "Mussum, Ipsum, cacilds vidis...",
"created_at": "1495928417",
"media": {
"url":"http://news/media/65815736...
},
"user": {
"name": "Mad Max"
},
"comments": [
//...
]
}
]
História
Fevereiro 2012
Protótipo SuperGraph
História
Desenvolvimento inicial
História
Facebook Newsfeed API para iOS
Agosto 2012
História
Evolução
História
Primeira aparição pública
Janeiro 2015
História
Refactoring
História
Open Source
Julho 2015
História
Feedback da comunidade
História
Release
Setembro 2016
GraphQL.org
$ composer require webonyx/graphql-php
http://webonyx.github.io/graphql-php/
Schema
Descreve os dados
Cardápio
$userType = new ObjectType([
'name' => 'User',
'fields' => [
'id' => Type::nonNull(Type::id()),
'name' => Type::nonNull(Type::string()),
'email' => Type::nonNull(Type::string()),
'role' => Type::nonNull(Type::string()),
'created_at' => Type::nonNull(Type::int())
]
]);
$mediaType = new ObjectType([
'name' => 'Media',
'fields' => [
'id' => Type::nonNull(Type::id()),
'type' => Type::nonNull(Type::string()),
'filename' => Type::nonNull(Type::string()),
'size' => Type::nonNull(Type::string()),
'url' => Type::nonNull(Type::string())
]
]);
$commentType = new ObjectType([
'name' => 'Comment',
'fields' => [
'id' => Type::nonNull(Type::id()),
'text' => Type::nonNull(Type::string()),
'created_at' => Type::nonNull(Type::int()),
'author' => Type::nonNull(new ObjectType([
'name' => 'Author',
'fields' => [
'name' => Type::string(),
'email' => Type::string()
]
]))
]
]);
$newsType = new ObjectType([
'name' => 'News',
'fields' => [
'id' => Type::nonNull(Type::id()),
'user' => Type::nonNull($userType),
'media' => $mediaType,
'title' => Type::nonNull(Type::string()),
'excerpt' => Type::string(),
'text' => Type::nonNull(Type::string()),
'created_at' => Type::nonNull(Type::int()),
'status' => Type::nonNull(Type::string()),
'comments' => Type::listOf($commentType)
]
]);
Um serviço
GraphQL
é composto por
TYPES,
que por sua vez são
compostos por
FIELDS
Types
Int
Float
Boolean
String
ID
Object
Modificadores
List
Non-Null
Root Types
Type System
São tipos especiais de
objetos que ficam na raiz
do schema
Enquanto a MUTATION
é opcional, a QUERY
deve estar presente em
todo schema GraphQL
$mutationType = new ObjectType([
'name' => 'Mutation',
'fields' => [
'registerUser' => $registerUserMutation,
'registerComment' => $registerCommentMutation,
'reportContent' => $reportContentMutation
]
]);
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'news' => $newsQuery,
'categories' => $categoriesQuery,
'users' => $usersQuery
],
]);
$schema = new Schema([
'query' => $queryType,
'mutation' => $mutationType
]);
Queries & Mutations
QUERY
são operações de
consulta no serviço
query{
news(id: 1) {
title, text, created_at,
media {
url
},
user {
name
},
comments {
text,
author {
name
}
}
}
}
$newsQuery = [
'type' => Type::listOf($newsType),
'args' => [
'id' => Type::id()
],
'resolve' => function ($root, $args) {
if ($args['id'] ?? false) {
return NewsRepository::findBy(
['id' => $args['id']]
);
}
return NewsRepository::findAll();
}
];
MUTATION
são operações que
modificam os dados no
serviço
Nomenclatura imperativa
$registerUserMutation = [
'type' => $userType,
'args' => [
'name' => Type::nonNull(Type::string()),
'email' => Type::nonNull(Type::string()),
],
'resolve' => function ($root, $args) {
return NewsRepository::add($args);
}
];
mutation{
registerUser(
name: "Mark",
email: "mark@locaweb.com"
) {
id,
name,
email,
role,
created_at
}
}
Validação
Todo dado que entra
e sai de um serviço
GraphQL é
validado no
SCHEMA
mutation{
registerUser(name: 1) {
undefined_field
}
}
{
"errors": [
{
"message": "Argument "name" has invalid value 1.nExpected
type "String", found 1.",
"locations": [
{
"line": 2,
"column": 22
}
]
},
{
"message": "Cannot query field "undefined_field" on type
"User".",
"locations": [
{
"line": 3,
"column": 5
}
]
}
]
}
Resolvers
São os responsáveis por
resolver a QUERY, ou
MUTATION, e retornar as
informações
Todo FIELD possui um
RESOLVER, mesmo
que de forma
implícita
O RESOLVER de um FIELD
filho recebe o resultado
do FIELD pai como
argumento
$newsQuery = [
'type' => Type::listOf($newsType),
'args' => [
'id' => Type::id()
],
'resolve' => function ($root, $args) {
if ($args['id'] ?? false) {
return NewsRepository::findBy(
['id' => $args['id']]
);
}
return NewsRepository::findAll();
}
];
$newsType = new ObjectType([
'name' => 'News',
'fields' => [
'id' => Type::nonNull(Type::id()),
'user' => Type::nonNull($userType),
'media' => $mediaType,
'title' => Type::nonNull(Type::string()),
'excerpt' => [
'type' => Type::nonNull(Type::string()),
'resolve' => function ($news, $args) {
return 'Excerpt from: ' . $news->title;
}
],
'text' => Type::nonNull(Type::string()),
'created_at' => Type::nonNull(Type::int()),
'status' => Type::nonNull(Type::string()),
'comments' => Type::listOf($commentType )
]
]);
query {
news(id:1) {
id
title
excerpt
}
}
{
"data": {
"news": [
{
"id": "1",
"title": "Joesley rompe com ..." ,
"excerpt": "Excerpt from: Joesley
rompe com Tony Ramos"
}
]
}
}
Adeus versionamento
http://pudim.com.br/api/v1
Quando um FIELD for
depreciado, é sinalizada a
mudança no SCHEMA e
definido o substituto
Endpoint único
CLIENT first
Entrega mais poder de
fogo para o frontend
Cada cliente consome o
serviço de acordo com a
sua necessidade. PC,
Mobile, Integrações...
Documentação
Documentação
Introspecção
Todo serviço
GraphQL
provê a navegação
por todo o
SCHEMA
A API já nasce documentada
{
__type(name: "News") {
name
fields {
name
}
}
}
{
"data": {
"__type": {
"name": "News",
"fields": [
{ "name": "id" },
{ "name": "user" },
{ "name": "media" },
{ "name": "title" }
//...
]
}
}
}
Experiência de
desenvolvimento
superior
Adoção
http://graphql.org/users
Frontend
Frontend
Apollo
Relay
Vanilla Js
Talk is cheap.
Show me the code!
Dúvidas?
Valeu!
@brunodasneves
Referências
http://graphql.org/
https://www.youtube.com/watch?v=zVNrqo9XGOs
https://github.com/chentsulin/awesome-graphql
http://webonyx.github.io/graphql-php
https://github.com/Youshido/GraphQL
https://dev-blog.apollodata.com/

PHP Community Summit - Do REST ao GraphQL com PHP