This talk will introduce attendees to GraphQL and then dive into the intricacies of how we built the API. It’ll cover end-to-end the flow we provide to our developers and some of the specific considerations we took when making it a public API: * solving the N+1 data retrieval problem and query planning * structuring models for optimal data retrieval * when to use GraphQL This talk will also cover some of the specifics of hooking GraphQL into a service-oriented architecture, how it’s built into our infrastructure, and the advantages you gain by having GraphQL interface with your services.
12. Query
The representation of data you
want returned
query {
business(id: "yelp") {
name
rating
reviews {
text
}
hours {
...
}
}
}
13. Schema
The representation of your data
structure
class Business(ObjectType):
name = graphene.String()
alias = graphene.String()
reviews = graphene.List(Review)
def resolve_name(root, ...):
return "Yelp"
def resolve_alias(root, ...):
return "yelp-sf"
def resolve_reviews(root, ...):
return [
Review(...)
for review in reviews
]
14. Fields
The attributes available on your
schema
class Business(ObjectType):
name = graphene.String()
alias = graphene.String()
reviews = graphene.List(Review)
def resolve_name(root, ...):
return "Yelp"
def resolve_alias(root, ...):
return "yelp-sf"
def resolve_reviews(root, ...):
return [
Review(...)
for review in reviews
]
16. Resolvers
Functions that retrieve data for a
specific field in a schema
class Business(ObjectType):
name = graphene.String()
alias = graphene.String()
reviews = graphene.List(Review)
def resolve_name(root, ...):
return "Yelp"
def resolve_alias(root, ...):
return "yelp-sf"
def resolve_reviews(root, ...):
return [
Review(...)
for review in reviews
]
41. The N+1 Problem
The inefficient loading of data by making
individual, sequential queries
cats = load_cats()
cat_hats = [
load_hats_for_cat(cat)
for cat in cats
]
# SELECT * FROM cat WHERE ...
# SELECT * FROM hat WHERE catID = 1
# SELECT * FROM hat WHERE catID = 2
# SELECT * FROM hat WHERE catID = ...
42. query {
b1: business(id: "yelp") {
name
}
b2: business(id: "moma") {
name
}
b3: business(id: "sushi") {
name
}
b4: business(id: "poke") {
name
}
b5: business(id: "taco") {
name
}
b6: business(id: "pizza") {
name
}
}
GET /internalapi/yelp
GET /internalapi/moma
GET /internalapi/sushi
GET /internalapi/poke
GET /internalapi/taco
GET /internalapi/pizza
43. query {
b1: business(id: "yelp") {
name
}
b2: business(id: "moma") {
name
}
b3: business(id: "sushi") {
name
}
b4: business(id: "poke") {
name
}
b5: business(id: "taco") {
name
}
b6: business(id: "pizza") {
name
}
}
GET /internalapi/yelp
GET /internalapi/moma
GET /internalapi/sushi
GET /internalapi/poke
GET /internalapi/taco
GET /internalapi/pizza
44. Dataloaders!
• An abstraction layer to load data in your resolvers
• Handle batching ids and deferring execution until all of your data has been
aggregated
45. query {
b1: business(id: "yelp") {
name
}
b2: business(id: "moma") {
name
}
b3: business(id: "sushi") {
name
}
b4: business(id: "poke") {
name
}
b5: business(id: "taco") {
name
}
b6: business(id: "pizza") {
name
}
}
GET /internalapi/yelp
GET /internalapi/moma
GET /internalapi/sushi
GET /internalapi/poke
GET /internalapi/taco
GET /internalapi/pizza
46. query {
b1: business(id: "yelp") {
name
}
b2: business(id: "moma") {
name
}
b3: business(id: "sushi") {
name
}
b4: business(id: "poke") {
name
}
b5: business(id: "taco") {
name
}
b6: business(id: "pizza") {
name
}
}
GET /internalapi/yelp,moma,sushi,poke,
77. Normally
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
GET https://api.yelp.com/v3/search
81. Node-based
• Count individual nodes returned by
the request sent to the API
POST https://api.yelp.com/v3/graphql
query {
search(term: "burrito", location: "sf") {
business {
name
reviews {
rating
text
}
}
}
}
82. Node-based
• Count individual nodes returned by
the request sent to the API
POST https://api.yelp.com/v3/graphql
query {
search(term: "burrito", location: "sf") {
business {
name
reviews {
rating
text
}
}
}
}
83. Node-based
• Count individual nodes returned by
the request sent to the API
POST https://api.yelp.com/v3/graphql
query {
search(term: "burrito", location: "sf") {
business {
name
reviews {
rating
text
}
}
}
}
84. Node-based
• Count individual nodes returned by
the request sent to the API
POST https://api.yelp.com/v3/graphql
query {
search(term: "burrito", location: "sf") {
business {
name
reviews {
rating
text
}
}
}
}
85. Field-based
• Count each individual field returned
by the request sent to the API
POST https://api.yelp.com/v3/graphql
query {
search(term: "burrito", location: "sf") {
business {
name
id
}
}
}
86. Field-based
• Count each individual field returned
by the request sent to the API
{
"data": {
"search": {
"business": [
{
"name": "El Farolito",
"id": "el-farolito-san-francisco-2"
},
{
"name": "La Taqueria",
"id": "la-taqueria-san-francisco-2"
},
{
"name": "Taqueria Guadalajara",
"id": "taqueria-guadalajara-san-francisco"
},
{
"name": "Taqueria Cancún",
"id": "taqueria-cancún-san-francisco-5"
},
{
"name": "Little Taqueria",
"id": "little-taqueria-san-francisco"
},
{
"name": "Pancho Villa Taqueria",
"id": "pancho-villa-taqueria-san-francisco"
},
{
"name": "Tacorea",
"id": "tacorea-san-francisco"
},
{
"name": "El Burrito Express - San Francisco",
"id": "el-burrito-express-san-francisco-san-francisco"
},
{
"name": "El Burrito Express",
"id": "el-burrito-express-san-francisco"
},
...
]
}
}
87. Field-based
• Count each individual field returned
by the request sent to the API
{
"data": {
"search": {
"business": [
{
"name": "El Farolito",
"id": "el-farolito-san-francisco-2"
},
{
"name": "La Taqueria",
"id": "la-taqueria-san-francisco-2"
},
{
"name": "Taqueria Guadalajara",
"id": "taqueria-guadalajara-san-francisco"
},
{
"name": "Taqueria Cancún",
"id": "taqueria-cancún-san-francisco-5"
},
{
"name": "Little Taqueria",
"id": "little-taqueria-san-francisco"
},
{
"name": "Pancho Villa Taqueria",
"id": "pancho-villa-taqueria-san-francisco"
},
{
"name": "Tacorea",
"id": "tacorea-san-francisco"
},
{
"name": "El Burrito Express - San Francisco",
"id": "el-burrito-express-san-francisco-san-francisco"
},
{
"name": "El Burrito Express",
"id": "el-burrito-express-san-francisco"
},
...
]
}
}
88. Securing the API
• Bulk endpoints to minimize the number of queries
• Network-level caching
• Daily rate limiting
• Limiting the maximum query size
• Per-resolver level authentication
• Persisted queries
89. Securing the API
• Bulk endpoints to minimize the number of queries
• Network-level caching
• Daily rate limiting
• Limiting the maximum query size
• Per-resolver level authentication
• Persisted queries
90. class MaxQuerySizeMiddleware:
MAX_SIZE = 2000
def __init__(self):
resolvers_executed = 0
def resolve(self, next, root, info, **args):
# did we hit the max for this query? nope
if resolvers_executed <= MAX_SIZE:
self.resolvers_executed += 1
return next(root, info, **args)
# we hit the max for this query
return None