G R A P H Q L , 

L’ AV E N I R D U R E S T ?
F R A N Ç O I S Z A N I N O T T O - @ f r a n c o i s z
G R A P H Q L
g r a f k ɥ ɛ l
G R A S K A F K A E C U E L L E
L E P R O B L È M E
PA R T I E 1 / 6
R E S T
1. GET /user
2. GET /tweets
3. GET /users?ids=[123,456,789,…]
4. GET /tweet_stats?ids=[123,456,789,…]
5. GET /notifications
6. POST /views
B R E A K I N G C H A N G E
R E S T I N P E A C E
• Trop de requêtes par page
• Mauvaise performance sur mobile
• Pas de standard
• Pas de schema
• Evolution impossible
• Actions limitées au CRUD
L E S A U T R E S
C A N D I D AT S
PA R T I E 2 / 6
R E S T + +
GET /tweets?include=author&fields=[id,date,body]
S Q L O V E R H T T P
GET /data?query=

SELECT t.id, t.date,t.body,a.name

FROM tweets t LEFT JOIN author a 

ON tweet.author_id = author.id

LIMIT 10
P R O T O C O L B U F F E R S
FA L C O R
L E S A I N T G R A A L
• Langage de requêtage déclaratif
• Reposant sur du Remote Procedure Call
• Typage fort, schema
• Support des agrégats
• Standardisé
• Non lié à HTTP
D É C O U V R E Z
G R A P H Q L
PA R T I E 3 / 6
POST / HTTP 1.1
Host: http://graphql.acme.com/
Content-Type: application/graphql
{
getTweet(id: 123) { id body date }
}
HTTP/1.1 200 OK
{
"data": {
"getTweet": {
"id": "123",
"body": "Lorem Ipsum",
"date": "2017-07-14"
}
}
}
GET /tweets/123 HTTP 1.1
Host: http://rest.acme.com/
HTTP/1.1 200 OK
{
"id": 123,
"body": "Lorem Ipsum",
"user_id": 456,
"views": 45,
"date": "2017-07-14",
// etc.
}
RequêteRéponse
REST GraphQL
POST / HTTP 1.1
Host: http://graphql.acme.com/
Content-Type: application/graphql
{
getTweets(limit: 10, sortField: "date", sortOrder: "DESC") {
id
body
date
}
getUser {
fullName
}
getNotificationsMeta {
count
}
}
Requête
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": {
"getTweets": [
{
"id": "752",
"body": "The guy next to me is listening...",
"date": "2017-07-15T13:17:42.772Z",
},
{
"id": "123",
"body": "The Espionnage Act was designed to...",
"date": "2017-07-14T12:44:17.449Z"
},
// etc.
],
"getUser": {
"fullName": "John Doe"
},
"getNotificationsMeta": {
"count": 12
}
}
}
Réponse
POST / HTTP 1.1
Host: http://graphql.acme.com/
Content-Type: application/graphql
{
getTweets(limit: 10, sortField: "date", sortOrder: "DESC") {
id
body
date
Author {
username
fullName
avatarUrl
}
Stat {
nbResponses
nbRetweets
nbLikes
}
}
getUser {
fullName
}
getNotificationsMeta {
count
}
}
Requête
POST / HTTP 1.1
Host: http://graphql.acme.com/
Content-Type: application/graphql
{
getTweets(limit: 10, sortField: "date", sortOrder: "DESC") {
id
body
date
Author {
username
fullName
avatarUrl
}
Stat {
nbResponses
nbRetweets
nbLikes
}
}
getUser {
fullName
}
getNotificationsMeta {
count
}
}
Requête
{
"data": {
"getTweets": [
{
"id": "752",
"body": "The guy next to me is listening...",
"date": "2017-07-15T13:17:42.772Z",
"Author": {
"username": "quantian1",
"fullName": "Quantian",
"avatarUrl": "https://amce.com/avatars/34345745634",
},
"Stat": {
"nbResponses": 3
"nbRetweets": 4,
"nbLikes": 40
}
},
// etc.
],
"getUser": {
"fullName": "John Doe"
},
"getNotificationsMeta": {
"count": 12
}
}
}
Réponse
POST / HTTP 1.1
Host: http://graphql.acme.com/
Content-Type: application/graphql
{
getTweets(limit: 10, sortField: "date", sortOrder: "DESC") {
id
body
date
Author {
username
fullName
avatarUrl
}
Stat {
nbResponses
nbRetweets
nbLikes
}
}
getUser {
fullName
}
getNotificationsMeta {
count
}
}
Requête
# entry points
type Query {
getTweet(id: ID!): Tweet
getTweets(limit: Int, sortField: String, sortOrder: String): [Tweet]
getUser: User
getNotificationsMeta: Meta
}
# custom types
type Tweet {
id: ID!
body: String
date: Date
Author: User
Stats: Stat
}
type User {
id: ID!
username: String
firstName: String
lastName: String
fullName: String
name: String @deprecated
# entry points
type Query {
getTweet(id: ID!): Tweet
getTweets(limit: Int, sortField: String, sortOrder: String): [Tweet]
getUser: User
getNotificationsMeta: Meta
}
# custom types
type Tweet {
id: ID!
body: String
date: Date
Author: User
Stats: Stat
}
type User {
id: ID!
username: String
firstName: String
lastName: String
fullName: String
name: String @deprecated
POST / HTTP 1.1
Host: http://graphql.acme.com/
Content-Type: application/graphql
{
getTweets(limit: 10, sortField: "date", sortOrder: "DESC") {
id
body
date(timezone: "UTC +2")
Author {
username
fullName
avatarUrl
TopTweets(limit: 10) {
body
date(timezone: "UTC +2 »)
Stats {
nbResponses
nbRetweets
}
}
}
Stat {
nbResponses
nbRetweets
}
}
}
Requête
L E L A N G A G E
G R A P H Q L
• Types
• Champs
• Requêtes, Mutations, Abonnements
• Variables
• Fragments
• Directives
C O M M E N T
G R A P H Q L
R É P O N D A V O S
D E M A N D E S
PA R T I E 4 / 6
type Query {
getTweet(id: ID!): Tweet
getTweets(limit: Int): [Tweet]
}
type Tweet {
id: ID!
body: String
Author: User
}
type User {
fullName: String
}
const resolvers = {
Query: {
getTweet: (_, params) => tweets.find(t => t.id == params.id),
getTweets: (_, params) => tweets.slice(0, params.limit),
},
Tweet: {
id: tweet => tweet.id,
body: tweet => tweet.body,
Author: tweet => users.find(a => a == tweet.author_id),
},
User: {
fullName: user => `${user.first_name} ${user.last_name}`,
},
};
{
getTweet(id: "1") {
id
body
Author {
fullName
}
}
}
getTweet: (_, params) => tweets.find(t => t.id == params.id)
(null, { id: 1 }) {
id: 1,
body: 'Lorem Ipsum’,
author_id: 10
}
1
{
getTweet(id: "1") {
id
body
Author {
fullName
}
}
}
Tweet: {
id: tweet => tweet.id,
body: tweet => tweet.body,
Author: tweet => users.find(a => a == tweet.author_id),
}
{ id: 1, body: 'Lorem Ipsum’, author_id: 10 }1
2
{
getTweet(id: "1") {
id
body
Author {
fullName
}
}
}
1
2 { id: ‘1’, body: 'Lorem Ipsum’, Author: {
id: 10,
first_name: ‘John',
last_name: 'Doe'
}
}
{ id: 1, body: 'Lorem Ipsum’, author_id: 10 }
{
getTweet(id: "1") {
id
body
Author {
fullName
}
}
}
1
2
User: {
fullName: user => `${user.first_name} ${user.last_name}`,
}
3
{ id: 1, body: 'Lorem Ipsum’, author_id: 10 }
{ id: ‘1’, body: 'Lorem Ipsum’, Author: {
id: 10,
first_name: ‘John',
last_name: 'Doe'
}
}
1
2
{ id: ‘1’, body: 'Lorem Ipsum’, Author: {

fullName: ‘John Doe'

}
}
{ id: ‘1’, body: 'Lorem Ipsum’, Author: {
id: 10,
first_name: ‘John',
last_name: 'Doe'
}
}
{ id: 1, body: 'Lorem Ipsum’, author_id: 10 }
3
{
getTweet(id: "1") {
id
body
Author {
fullName
}
}
}
AVA I L A B L E I N Y O U R
S E R V E R L A N G U A G E
• JavaScript
• PHP
• Ruby
• Python
• Go
• Java
• Scala
• C#
• Elixir
• Fortran
• BrainFuck
• etc…
const query = `
{
getTweet(id: "1") {
id
body
Author {
name
}
}
}`;
const headers = new Headers();
myHeaders.append('Content-Type', 'application/graphql');
fetch(‘/graphql’, { method: 'POST', body: query, headers });
.then(response => response.json)
.then(response => response.data.getTweet)
.then(tweet => ...);
import React from 'react';
import { gql, graphql } from 'react-apollo';
import LinearProgress from 'material-ui';
const Tweet = ({ data: { loading, tweet } }) => (
<div>
{loading ? (
<LinearProgress />
) : (
<div>
<img src={tweet.author.avatar} className="avatar" />
<span className="author">{tweet.Author.name}</span>
<div className="body">{tweet.body}</div>
</div>
)}
</div>
);
const query = gql`{
getTweet(id: "1") {
id
body
Author {
name
}
}
}`;
export default graphql(query)(App);
AVA I L A B L E I N Y O U R
C L I E N T L A N G U A G E
• Vue.js
• React.js
• Angular.js
• Meteor.js
• Objective-C
• Swift
• Expo
• Java
• Kotlin
• Dart
• Fart
• etc…
T O U T E S T P R Ê T P O U R
V O U S A C C U E I L L I R
• Simple
• Stable
• Sécurisé
• Performant
• Documenté
• Supporté
G R A P H Q L , A N G E
O U D É M O N ?
PA R T I E 5 / 6
#diversité
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 500 Internal Server Error
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 500 Internal Server Error
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
POST /graphql 200 OK
{
Tweets(limit: 100) {
Author {
Tweets(limit: 100) {
Author {
Tweets(limit: 100) {
Author {
Tweets(limit: 100) {
Author {
name
}
}
}
}
}
}
}
}
}
Client Server
query
Client Server
id
Dictionary Dictionary
query id id query
PersistedQueries
G R A P H Q L , C ’ E S T
P O U R Q U I ?
PA R T I E 6 / 6
Q U E L T Y P E D E D E V I C E U T I L I S E N T V O S C L I E N T S ?
Q U E S T I O N # 1
D E S K T O P
M O B I L E 

E T / O U D E S K T O P
L A P E R F O R M A N C E C Ô T É C L I E N T
E S T- E L L E I M P O R TA N T E ?
Q U E S T I O N # 2
N O N
O U I
C O M B I E N D E R O U T E S A V O T R E A P I R E S T ?
Q U E S T I O N # 3
5 A U P L U S
A U M O I S 6
Q U E L L E E S T L A D U R É E D E V I E D E V O T R E A P I ?
Q U E S T I O N # 4
A U M O I N S 

U N A N Q U E L Q U E S 

M O I S
Q U E L E S T L E N I V E A U D E C O M P L E X I T É 

D E V O T R E A P I ?
Q U E S T I O N # 5
S I M P L E
C O M P L E X E
S U R L A P L U S C O M P L E X E S D E S PA G E S D U C L I E N T,
C O M B I E N D E P E R S I S T E N C E S S O N T A P P E L E E S ?
Q U E S T I O N # 6
U N
P L U S D E U N
AV E Z - V O U S D E S R È G L E S D E C A C H E C O M P L E X E S ?
Q U E S T I O N # 7
N O N
O U I
C O M M E N T T R AVA I L L E N T V O S É Q U I P E S 

F R O N T E T B A C K ?
Q U E S T I O N # 8
B A C K L O G 

C O M M U N
S I L O
C O M B I E N D E D É V E L O P P E U R S ( F R O N T + B A C K )
T R AVA I L L E N T AV E C L’ A P I ?
Q U E S T I O N # 9
A U M O I N S 

C I N Q
U N À Q U AT R E
Q U E L E S T V O T R E T Y P E D E L A N G A G E P R É F É R É ?
Q U E S T I O N # 1 0
F O RT E M E N T 

T Y P É
FA I B L E M E N T 

T Y P É
M E R C I ! D E S
Q U E S T I O N S ?
F R A N Ç O I S Z A N I N O T T O
@ f r a n c o i s z

GraphQL, l'avenir du REST ?

  • 1.
    G R AP H Q L , 
 L’ AV E N I R D U R E S T ? F R A N Ç O I S Z A N I N O T T O - @ f r a n c o i s z
  • 3.
    G R AP H Q L g r a f k ɥ ɛ l G R A S K A F K A E C U E L L E
  • 4.
    L E PR O B L È M E PA R T I E 1 / 6
  • 5.
  • 7.
    1. GET /user 2.GET /tweets 3. GET /users?ids=[123,456,789,…] 4. GET /tweet_stats?ids=[123,456,789,…] 5. GET /notifications 6. POST /views
  • 13.
    B R EA K I N G C H A N G E
  • 19.
    R E ST I N P E A C E • Trop de requêtes par page • Mauvaise performance sur mobile • Pas de standard • Pas de schema • Evolution impossible • Actions limitées au CRUD
  • 20.
    L E SA U T R E S C A N D I D AT S PA R T I E 2 / 6
  • 21.
    R E ST + + GET /tweets?include=author&fields=[id,date,body]
  • 23.
    S Q LO V E R H T T P GET /data?query=
 SELECT t.id, t.date,t.body,a.name
 FROM tweets t LEFT JOIN author a 
 ON tweet.author_id = author.id
 LIMIT 10
  • 24.
    P R OT O C O L B U F F E R S
  • 25.
    FA L CO R
  • 26.
    L E SA I N T G R A A L • Langage de requêtage déclaratif • Reposant sur du Remote Procedure Call • Typage fort, schema • Support des agrégats • Standardisé • Non lié à HTTP
  • 27.
    D É CO U V R E Z G R A P H Q L PA R T I E 3 / 6
  • 28.
    POST / HTTP1.1 Host: http://graphql.acme.com/ Content-Type: application/graphql { getTweet(id: 123) { id body date } } HTTP/1.1 200 OK { "data": { "getTweet": { "id": "123", "body": "Lorem Ipsum", "date": "2017-07-14" } } } GET /tweets/123 HTTP 1.1 Host: http://rest.acme.com/ HTTP/1.1 200 OK { "id": 123, "body": "Lorem Ipsum", "user_id": 456, "views": 45, "date": "2017-07-14", // etc. } RequêteRéponse REST GraphQL
  • 30.
    POST / HTTP1.1 Host: http://graphql.acme.com/ Content-Type: application/graphql { getTweets(limit: 10, sortField: "date", sortOrder: "DESC") { id body date } getUser { fullName } getNotificationsMeta { count } } Requête
  • 31.
    HTTP/1.1 200 OK Content-Type:application/json { "data": { "getTweets": [ { "id": "752", "body": "The guy next to me is listening...", "date": "2017-07-15T13:17:42.772Z", }, { "id": "123", "body": "The Espionnage Act was designed to...", "date": "2017-07-14T12:44:17.449Z" }, // etc. ], "getUser": { "fullName": "John Doe" }, "getNotificationsMeta": { "count": 12 } } } Réponse
  • 32.
    POST / HTTP1.1 Host: http://graphql.acme.com/ Content-Type: application/graphql { getTweets(limit: 10, sortField: "date", sortOrder: "DESC") { id body date Author { username fullName avatarUrl } Stat { nbResponses nbRetweets nbLikes } } getUser { fullName } getNotificationsMeta { count } } Requête
  • 34.
    POST / HTTP1.1 Host: http://graphql.acme.com/ Content-Type: application/graphql { getTweets(limit: 10, sortField: "date", sortOrder: "DESC") { id body date Author { username fullName avatarUrl } Stat { nbResponses nbRetweets nbLikes } } getUser { fullName } getNotificationsMeta { count } } Requête
  • 35.
    { "data": { "getTweets": [ { "id":"752", "body": "The guy next to me is listening...", "date": "2017-07-15T13:17:42.772Z", "Author": { "username": "quantian1", "fullName": "Quantian", "avatarUrl": "https://amce.com/avatars/34345745634", }, "Stat": { "nbResponses": 3 "nbRetweets": 4, "nbLikes": 40 } }, // etc. ], "getUser": { "fullName": "John Doe" }, "getNotificationsMeta": { "count": 12 } } } Réponse
  • 37.
    POST / HTTP1.1 Host: http://graphql.acme.com/ Content-Type: application/graphql { getTweets(limit: 10, sortField: "date", sortOrder: "DESC") { id body date Author { username fullName avatarUrl } Stat { nbResponses nbRetweets nbLikes } } getUser { fullName } getNotificationsMeta { count } } Requête
  • 40.
    # entry points typeQuery { getTweet(id: ID!): Tweet getTweets(limit: Int, sortField: String, sortOrder: String): [Tweet] getUser: User getNotificationsMeta: Meta } # custom types type Tweet { id: ID! body: String date: Date Author: User Stats: Stat } type User { id: ID! username: String firstName: String lastName: String fullName: String name: String @deprecated
  • 41.
    # entry points typeQuery { getTweet(id: ID!): Tweet getTweets(limit: Int, sortField: String, sortOrder: String): [Tweet] getUser: User getNotificationsMeta: Meta } # custom types type Tweet { id: ID! body: String date: Date Author: User Stats: Stat } type User { id: ID! username: String firstName: String lastName: String fullName: String name: String @deprecated
  • 43.
    POST / HTTP1.1 Host: http://graphql.acme.com/ Content-Type: application/graphql { getTweets(limit: 10, sortField: "date", sortOrder: "DESC") { id body date(timezone: "UTC +2") Author { username fullName avatarUrl TopTweets(limit: 10) { body date(timezone: "UTC +2 ») Stats { nbResponses nbRetweets } } } Stat { nbResponses nbRetweets } } } Requête
  • 44.
    L E LA N G A G E G R A P H Q L • Types • Champs • Requêtes, Mutations, Abonnements • Variables • Fragments • Directives
  • 46.
    C O MM E N T G R A P H Q L R É P O N D A V O S D E M A N D E S PA R T I E 4 / 6
  • 47.
    type Query { getTweet(id:ID!): Tweet getTweets(limit: Int): [Tweet] } type Tweet { id: ID! body: String Author: User } type User { fullName: String }
  • 49.
    const resolvers ={ Query: { getTweet: (_, params) => tweets.find(t => t.id == params.id), getTweets: (_, params) => tweets.slice(0, params.limit), }, Tweet: { id: tweet => tweet.id, body: tweet => tweet.body, Author: tweet => users.find(a => a == tweet.author_id), }, User: { fullName: user => `${user.first_name} ${user.last_name}`, }, };
  • 50.
    { getTweet(id: "1") { id body Author{ fullName } } } getTweet: (_, params) => tweets.find(t => t.id == params.id) (null, { id: 1 }) { id: 1, body: 'Lorem Ipsum’, author_id: 10 } 1
  • 51.
    { getTweet(id: "1") { id body Author{ fullName } } } Tweet: { id: tweet => tweet.id, body: tweet => tweet.body, Author: tweet => users.find(a => a == tweet.author_id), } { id: 1, body: 'Lorem Ipsum’, author_id: 10 }1 2
  • 52.
    { getTweet(id: "1") { id body Author{ fullName } } } 1 2 { id: ‘1’, body: 'Lorem Ipsum’, Author: { id: 10, first_name: ‘John', last_name: 'Doe' } } { id: 1, body: 'Lorem Ipsum’, author_id: 10 }
  • 53.
    { getTweet(id: "1") { id body Author{ fullName } } } 1 2 User: { fullName: user => `${user.first_name} ${user.last_name}`, } 3 { id: 1, body: 'Lorem Ipsum’, author_id: 10 } { id: ‘1’, body: 'Lorem Ipsum’, Author: { id: 10, first_name: ‘John', last_name: 'Doe' } }
  • 54.
    1 2 { id: ‘1’,body: 'Lorem Ipsum’, Author: {
 fullName: ‘John Doe'
 } } { id: ‘1’, body: 'Lorem Ipsum’, Author: { id: 10, first_name: ‘John', last_name: 'Doe' } } { id: 1, body: 'Lorem Ipsum’, author_id: 10 } 3 { getTweet(id: "1") { id body Author { fullName } } }
  • 57.
    AVA I LA B L E I N Y O U R S E R V E R L A N G U A G E • JavaScript • PHP • Ruby • Python • Go • Java • Scala • C# • Elixir • Fortran • BrainFuck • etc…
  • 62.
    const query =` { getTweet(id: "1") { id body Author { name } } }`; const headers = new Headers(); myHeaders.append('Content-Type', 'application/graphql'); fetch(‘/graphql’, { method: 'POST', body: query, headers }); .then(response => response.json) .then(response => response.data.getTweet) .then(tweet => ...);
  • 63.
    import React from'react'; import { gql, graphql } from 'react-apollo'; import LinearProgress from 'material-ui'; const Tweet = ({ data: { loading, tweet } }) => ( <div> {loading ? ( <LinearProgress /> ) : ( <div> <img src={tweet.author.avatar} className="avatar" /> <span className="author">{tweet.Author.name}</span> <div className="body">{tweet.body}</div> </div> )} </div> ); const query = gql`{ getTweet(id: "1") { id body Author { name } } }`; export default graphql(query)(App);
  • 64.
    AVA I LA B L E I N Y O U R C L I E N T L A N G U A G E • Vue.js • React.js • Angular.js • Meteor.js • Objective-C • Swift • Expo • Java • Kotlin • Dart • Fart • etc…
  • 67.
    T O UT E S T P R Ê T P O U R V O U S A C C U E I L L I R • Simple • Stable • Sécurisé • Performant • Documenté • Supporté
  • 68.
    G R AP H Q L , A N G E O U D É M O N ? PA R T I E 5 / 6
  • 73.
  • 80.
    POST /graphql 200OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 500 Internal Server Error POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 500 Internal Server Error POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK POST /graphql 200 OK
  • 82.
    { Tweets(limit: 100) { Author{ Tweets(limit: 100) { Author { Tweets(limit: 100) { Author { Tweets(limit: 100) { Author { name } } } } } } } } }
  • 83.
    Client Server query Client Server id DictionaryDictionary query id id query PersistedQueries
  • 85.
    G R AP H Q L , C ’ E S T P O U R Q U I ? PA R T I E 6 / 6
  • 87.
    Q U EL T Y P E D E D E V I C E U T I L I S E N T V O S C L I E N T S ? Q U E S T I O N # 1 D E S K T O P M O B I L E 
 E T / O U D E S K T O P
  • 88.
    L A PE R F O R M A N C E C Ô T É C L I E N T E S T- E L L E I M P O R TA N T E ? Q U E S T I O N # 2 N O N O U I
  • 89.
    C O MB I E N D E R O U T E S A V O T R E A P I R E S T ? Q U E S T I O N # 3 5 A U P L U S A U M O I S 6
  • 90.
    Q U EL L E E S T L A D U R É E D E V I E D E V O T R E A P I ? Q U E S T I O N # 4 A U M O I N S 
 U N A N Q U E L Q U E S 
 M O I S
  • 91.
    Q U EL E S T L E N I V E A U D E C O M P L E X I T É 
 D E V O T R E A P I ? Q U E S T I O N # 5 S I M P L E C O M P L E X E
  • 92.
    S U RL A P L U S C O M P L E X E S D E S PA G E S D U C L I E N T, C O M B I E N D E P E R S I S T E N C E S S O N T A P P E L E E S ? Q U E S T I O N # 6 U N P L U S D E U N
  • 93.
    AV E Z- V O U S D E S R È G L E S D E C A C H E C O M P L E X E S ? Q U E S T I O N # 7 N O N O U I
  • 94.
    C O MM E N T T R AVA I L L E N T V O S É Q U I P E S 
 F R O N T E T B A C K ? Q U E S T I O N # 8 B A C K L O G 
 C O M M U N S I L O
  • 95.
    C O MB I E N D E D É V E L O P P E U R S ( F R O N T + B A C K ) T R AVA I L L E N T AV E C L’ A P I ? Q U E S T I O N # 9 A U M O I N S 
 C I N Q U N À Q U AT R E
  • 96.
    Q U EL E S T V O T R E T Y P E D E L A N G A G E P R É F É R É ? Q U E S T I O N # 1 0 F O RT E M E N T 
 T Y P É FA I B L E M E N T 
 T Y P É
  • 101.
    M E RC I ! D E S Q U E S T I O N S ? F R A N Ç O I S Z A N I N O T T O @ f r a n c o i s z