GraphQL & Apollo
https://goo.gl/qYrg4q
Web Service
A l'ancienne
RESTful
{
"movies": [
"title": "Star Wars",
"year": 1977,
"directorId": 0,
]
}
GET http://api.example.com/v1/movies
RESTful
{
"firstname": "George",
"lastname": "Lucas",
}
GET http://api.example.com/v1/directors/0
Waterfall
SOAP
HTTP Method | URL |
---|---|
POST | http://api.example.com/v1/movies |
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="..."
xmlns:m="...">
<soap:Header>
</soap:Header>
<soap:Body>
<m:GetAllMovies>
<m:Genre>sf</m:Genre>
</m:GetAllMovies>
</soap:Body>
</soap:Envelope>
<?xml version="1.0"?>
<soap:Envelope>
<soap:Header>
</soap:Header>
<soap:Body>
<m:GetAllMoviesResponse>
<m:Movie>
<m:Title>Star Wars</m:Title>
<m:Year>1977</m:Year>
<m:DirectorId>0</m:DirectorId>
</m:Movie>
</m:GetAllMoviesResponse>
</soap:Body>
</soap:Envelope>
GraphQL
Pourquoi
GraphQL ?
Fun Fact
Depuis plusieurs années
Vous utilisez déjà GraphQL
Origines
2012 - 2015
Pourquoi GraphQL ?
Le grand problème
GraphQL résout des problèmes bien réels
- Client mobile (latence)
- Plusieurs clients (ex: Web + iOS)
- Micro-services
- API REST devenue trop complexe
- Découplage Frontend ↔ Backend
REST n'est pas la réponse
REST n'est pas la réponse
- Ad-hoc
- Waterfall
- Taille des données
- Versions
- Grosses documentations
GraphQL améliore votre expérience de développeur
- Relation claire entre backends & frontends
- Moins de réunions et de problèmes de com
- Plus besoin d'écrire de doc
- Plus besoin de comprendre votre API
- Outils de développements avancés
GraphQL
Mais qu'est-ce donc exactement ?
Design
- Hiérarchie
- Orienté Produit
- Typage Fort
- Requêtes précises du client
- Introspection
Le client reçoit exactement ce qu'il a demandé
{ movie { title } }
{ "movie": { "title": "Star Wars" } }
Plusieurs ressources en une seule requête
{ movie { title director { firstname lastname } } }
MOVIES
DIRECTORS
Structuration des données avec des Types
{ movie { title director { firstname lastname } } }
type Movie { title: String director: Director } type Director { firstname: String lastname: String }
Évolutions sans version
type Movie { title: String @deprecated originalTitle: String director: Director releaseYear: Int }
type Movie { title: String director: Director }
Expérience développeur
GraphQL
Côté Serveur
Schema
- Description de l'API
- Typage Fort
- Utilisation explicite
- Contient des Types, Enums, ...
- Et des descriptions/commentaires !
Scalaires
-
Int
-
Float
-
String
-
Boolean
-
ID
-
<le vôtre ici>
Objets
type Movie { title: String! releaseYear: Int director: Person } type Person { name: String! age: Int! }
Interfaces
interface NamedEntity { name: String } type Person implements NamedEntity { name: String age: Int }
Unions
type Movie { title: String releaseYear: Int director: Person } type Person { name: String age: Int } union SearchResult = Movie | Person
Enums
enum Status { RUMORED IN_PRODUCTION RELEASED } type Movie { status: Status }
Opérations
schema { # Récupération query: Query # Modification mutation: Mutation # Temps-réel subscription: Subscription }
Arguments
type Query { movie(id: ID): Movie } type Movie { posterUrl(size: Int): String }
Lists
type Query { movies(genre: String): [Movie] } type Movie { actors: [Actor] }
Inputs
input ReviewInput { stars: Int! comment: String } type Review { id: ID! stars: Int! comment: String } type Mutation { addReview(movie: ID!, review: ReviewInput): Review }
Directives
type Movie { title: String @deprecated originalTitle: String director: Director releaseYear: Int }
GraphQL
Côté Client
Requête
query { movies(genre: "sf") { title director { name age } } }
{
"data": {
"movies": [
{
"title": "Star Wars",
"director": {
"name": "George Lucas",
"age": 72
}
}
]
}
}
Document
Résultat
Plusieurs Requêtes ?
{ movies(genre: "sf") { title } tvShows(genre: "sf") { title } }
{
"movies": [
{
"title": "Star Wars"
}
],
"tvShows": [
{
"title": "The Expanse"
}
]
}
Variables
query videos ($genre: String = "all") { movies(genre: $genre) { title } tvShows(genre: $genre) { title } } { genre: "sf" }
Directives
query videos ($onlyMovies: Boolean = false) { movies @include(if: true) { title } tvShows @skip(if: $onlyMovies) { title } }
Fragments
query { movies(genre: "sf") { ...movieFields } movies(genre: "action") { ...movieFields } } fragment movieFields on Movie { title releaseYear }
Inline Fragments
query { search { __typename title ... on Movies { releaseYear } ... on TvShow { currentSeason } } }
{
"search": [
{
"__typename": "Movie",
"title": "Star Wars",
"releaseYear": 1997
},
{
"__typename": "TvShow",
"title": "The Expanse",
"currentSeason": 2
}
]
}
Alias
query { searchResults: search { label: title ... on Movies { year: releaseYear } ... on TvShow { season: currentSeason } } }
{
"searchResults": [
{
"label": "Star Wars",
"year": 1997
},
{
"label": "The Expanse",
"season": 2
}
]
}
Mutations
mutation addReview($movie: ID!, $review: ReviewInput) { newReview: addReview (movie: $movie, review: $review) { id stars comment } }
{
"newReview": {
"id": "f6gt7DA42-1F",
"stars": 5,
"comment": "This movie is awesome!"
}
}
Introspection
query { __schema { types { name } } }
{
"__schema": {
"types": [
{
"name": "Movie"
},
{
"name": "TvShow"
}
]
}
}
Meteor Development Group
meteor.io
github.com/apollographql
- Client GraphQL
- JavaScript
- Android
- iOS
- Outils serveur
- express
- koa
- hapi
- restify
- AWS lambda
Apollo Optics
apollodata.com/optics
Apollo
Côté Serveur
Design
Serveur HTTP
Schema
Resolvers
Connectors
type
{...}
Schema
const typeDefs = `
# General entity
interface Entity {
id: ID!
name: String!
image: String
}
# Movie
type Movie implements Entity {
id: ID!
name: String!
image: String
genre: String!
releaseYear: Int
director: Director
}
`
Connectors
const collection = new DataBaseCollection()
export function getAll () {
return collection
}
export function getByGenre (genre) {
return collection.filter(i => i.genre === genre)
}
export function getById (id) {
return collection.find(i => i.id === id)
}
Resolvers
const resolvers = {
Movie: {
director: (movie) => Directors.getById(movie.directorId),
},
Query: {
movies: (root, { genre }) => Movies.getByGenre(genre),
movie: (root, { id }) => Movies.getById(id),
tvShows: (root, { genre }) => TvShows.getByGenre(genre),
tvShow: (root, { id }) => TvShows.getById(id),
search: (root, { text }) => Search.search(text),
},
}
Executable schema
import typeDefs from './type-defs'
import resolvers from './resolvers'
const jsSchema = makeExecutableSchema({
typeDefs,
resolvers,
})
export default jsSchema
Express Server
import { graphqlExpress, graphiqlExpress }
from 'graphql-server-express'
import schema from './schema'
const PORT = process.env.PORT || 3000
const app = express()
// GraphQL server
app.use('/graphql', graphqlExpress({ schema }))
// GraphiQL devtool
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}))
app.listen(PORT, () => console.log(`Listening on port ${PORT}`))
Done!
Apollo
Côté Client
Design
Query
Mutation
Subscription (Web socket)
.gql
Observable
query
Cache Normalisé
Apollo Client
import { ApolloClient, createNetworkInterface } from 'apollo-client'
// Create the apollo client
const apolloClient = new ApolloClient({
networkInterface: createNetworkInterface({
uri: 'http://localhost:3000/graphql',
transportBatching: true,
}),
connectToDevTools: true,
})
Query
import gql from 'graphql-tag'
const MOVIES_QUERY = gql`
query Movies($genre: String) {
movies (genre: $genre) {
id
name
image
genre
releaseYear
}
}`
apolloClient.query({
query: MOVIES_QUERY,
variables: { genre: "sf" },
}).then(result => {
console.log(result)
})
WatchQuery
const q = apolloClient.watchQuery({
query: MOVIES_QUERY,
})
const sub = q.subscribe({
next: (result) => console.log(result),
error: (error) => console.error(error),
})
// Later...
sub.unsubscribe()
Refetch
q.refetch()
q.setVariables({
genre: 'comics',
})
q.setOptions({
fetchPolicy: 'cache-only',
})
Pagination: FetchMore
q.fetchMore({
variables: {
page: this.page,
pageSize,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const newMovies = fetchMoreResult.paginatedMovies.movies
const hasMore = fetchMoreResult.paginatedMovies.hasMore
return {
paginatedMovies: {
// Merging the movie list
movies: [...previousResult.paginatedMovies.tags, ...newMovies],
hasMore,
},
}
},
})
Mutation
apolloClient.mutate({
mutation: gql`mutation ($label: String!) {
addTag(label: $label) {
id
label
}
}`,
variables: { label: newTag },
updateQueries: {
tagList: (previousResult, { mutationResult }) => ({
tags: [...previousResult.tags, mutationResult.data.addTag],
}),
},
optimisticResponse: {
__typename: 'Mutation',
addTag: {
__typename: 'Tag',
id: -1,
label: newTag,
},
},
}).then((data) => {
console.log(data)
}).catch((error) => {
console.error(error)
this.newTag = newTag
})
Apollo + Vue
github.com/Akryum/graphql-demo
conf.vuejs.org
Merci !
[FR] GraphQL & Apollo
By Guillaume Chau
[FR] GraphQL & Apollo
Avez-vous déjà utilisé ou créé des web services REST ou SOAP ? Voulez-vous quelque chose de différent ? Venez découvrir un nouveau genre d'API, à l'origine créé dans les laboratoires secrets de Facebook. J'ai nommé : GraphQL. Ce protocole permet de créer des web services modernes répondant aux besoins d'aujourd'hui : haute performance sur réseau mobile, forte évolutivité et très grande souplesse, usages émergents, données en temps réel, auto-documentation… Apollo GraphQL, un projet communautaire lancé et supporté par le Meteor Developement Group, nous permet d'utiliser facilement GraphQL à la fois côté front- et back-end. Le client Apollo est une alternative sérieuse et populaire à Relay, conçue pour fonctionner partout et offrant quelques fonctionnalités sympathiques comme un cache intelligent.Bonus: tout ceci sera concrétisé dans une application Vue !
- 8,430