tomer elmalem - graphql apis: rest in peace - codemotion milan 2017

97
GraphQL at Yelp REST in Peace Tomer Elmalem [email protected] @zehtomer

Upload: codemotion

Post on 21-Jan-2018

113 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

GraphQL at YelpREST in Peace

Tomer [email protected]

@zehtomer

Page 2: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Yelp’s MissionConnecting people with great

local businesses.

Page 3: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

In the beginning

Page 4: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

In the beginning

Page 5: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Enter GraphQL

Page 6: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { name alias rating } }

{ "data": { "business": { "name": "Yelp", "alias": "yelp-sf", "rating": 4.9 } } }

Page 7: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { name alias rating reviews { text } } }

{ "data": { "business": { "name": "Yelp", "alias": "yelp-sf", "rating": 4.9, "reviews": [{ "text": "some review" }] } } }

Page 8: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { name rating reviews { text } hours { ... } }}

{ "data": { "business": { "name": "Yelp", "rating": 4.9, "reviews": [{ "text": "some review" }], "hours": [{ ... }] } } }

Page 9: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ b1: business(id: "yelp") { name rating reviews { text } } b2: business(id: "sforza") { name rating reviews { text } }}

{ "data": { "b1": { "name": "Yelp", "rating": 4.9, "reviews": [{ "text": "some review" }] }, "b2": { "name": "Sforza Castle", "rating": 5.0, "reviews": [{ "text": "awesome art" }] } } }

Page 10: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { reviews { users { reviews { business { categories } } } } }}

{ "data": { "business": { "reviews": [{ "users": [{ "reviews": [ { "business": { "categories": ["food"] } }, { "business": { "categories": ["media"] } } ] }] }] } } }

Page 11: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Let’s start with some vocab

Page 12: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Query The representation of data you want returned

query { business(id: "yelp") { name rating reviews { text } hours { ... } }}

Page 13: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 ]

Page 14: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 ]

Page 15: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ "business": { "name": "Yelp", "alias": "yelp-sf", "rating": 4.9 } }

{ business(id: "yelp") { name alias rating }}

Page 16: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 ]

Page 17: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

So how does this all work?

Page 18: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Our Setup• Dedicated public API service• Python 3.6• Graphene (Python GraphQL library)• Pyramid + uWSGI• Complex microservices architecture• No attached database

Page 19: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Our Setup• Dedicated public API service• Python 3.6• Graphene (Python GraphQL library)• Pyramid + uWSGI• Complex microservices architecture• No attached database

Page 20: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { name reviews { text } }}

POST /v3/graphql

Page 21: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 22: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 23: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

def verify_api_access(wrapped):

def wrapper(context, request): access_token = _validate_authorization_header(request)

response = _validate_token( access_token, path, request.client_addr )

request.client = response

if response.valid: return wrapped(context, request) else: raise UnauthorizedAccessToken()

return wrapper

Page 24: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

def verify_api_access(wrapped):

def wrapper(context, request): access_token = _validate_authorization_header(request)

response = _validate_token( access_token, path, request.client_addr )

request.client = response

if response.valid: return wrapped(context, request) else: raise UnauthorizedAccessToken()

return wrapper

Page 25: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 26: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 27: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

business = graphene.Field( Business, alias=graphene.String(), )

search = graphene.Field( Businesses, term=graphene.String(), location=graphene.String(), # ... )

# ...

Page 28: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

business = graphene.Field( Business, alias=graphene.String(), )

search = graphene.Field( Businesses, term=graphene.String(), location=graphene.String(), # ... )

# ...

Page 29: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

# ...

@verify_limited_graphql_access('graphql') def resolve_business(root, args, context, info): alias = args.get('alias') internalapi_client = get_internal_api_client()

business = internalapi_client.business.get_business( business_alias=alias ).result()

return context['dataloaders'].businesses.load(business.id)

Page 30: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

# ...

@verify_limited_graphql_access('graphql') def resolve_business(root, args, context, info): alias = args.get('alias') internalapi_client = get_internal_api_client()

business = internalapi_client.business.get_business( business_alias=alias ).result()

return context['dataloaders'].businesses.load(business.id)

Page 31: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

# ...

@verify_limited_graphql_access('graphql') def resolve_business(root, args, context, info): alias = args.get('alias') internalapi_client = get_internal_api_client()

business = internalapi_client.business.get_business( business_alias=alias ).result()

return context['dataloaders'].businesses.load(business.id)

Page 32: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

# ...

@verify_limited_graphql_access('graphql') def resolve_business(root, args, context, info): alias = args.get('alias') internalapi_client = get_internal_api_client()

business = internalapi_client.business.get_business( business_alias=alias ).result()

return context['dataloaders'].businesses.load(business.id)

Page 33: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 34: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 35: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 36: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

# ...

@verify_limited_graphql_access('graphql') def resolve_business(root, args, context, info): alias = args.get('alias') internalapi_client = get_internal_api_client()

business = internalapi_client.business.get_business( business_alias=alias ).result()

return context['dataloaders'].businesses.load(business.id)

Page 37: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The Schema class Business(graphene.ObjectType):

name = graphene.String() reviews = graphene.List(Reviews)

def resolve_name(root, context, ...): return root.name

def resolve_reviews(root, context, ...): return [ Review(...) for review in root.reviews ]

Page 38: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Page 39: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Dataloaders…?

Page 40: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The N+1 Problem

Page 41: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 = ...

Page 42: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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/yelpGET /internalapi/momaGET /internalapi/sushiGET /internalapi/pokeGET /internalapi/tacoGET /internalapi/pizza

Page 43: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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/yelpGET /internalapi/momaGET /internalapi/sushiGET /internalapi/pokeGET /internalapi/tacoGET /internalapi/pizza

Page 44: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Dataloaders!• An abstraction layer to load data in your resolvers• Handle batching ids and deferring execution until all of your data has been

aggregated

Page 45: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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/yelpGET /internalapi/momaGET /internalapi/sushiGET /internalapi/pokeGET /internalapi/tacoGET /internalapi/pizza

Page 46: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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,

Page 47: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

The View @view_config( route_name='api.graphql', renderer='json', decorator=verify_api_access, ) def graphql(request): schema = graphene.Schema( query=Query, )

locale = request.headers.get( 'Accept-Language' )

context = { 'request': request, 'client': request.client, 'dataloaders': DataLoaders(locale), }

return schema.execute( request.body, context_value=context )

Page 48: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class DataLoaders:

def __init__(self, locale): self.businesses = BusinessDataLoader(locale) self.coordinates = CoordinatesDataLoader() self.hours = HoursDataLoader() self.photos = PhotosDataLoader() self.events = EventDataLoader() self.reviews = ReviewsDataLoader(locale) self.venues = VenueDataLoader()

Page 49: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Dataloader class BusinessDataLoader(DataLoader):

def __init__(self, locale, **kwargs): super().__init__(**kwargs) self._locale = locale

def batch_load_fn(self, biz_ids): businesses = get_businesses_info( biz_ids, self._locale ).result()

biz_id_map = self._biz_map( businesses )

return Promise.resolve([ biz_id_map.get(biz_id) for biz_id in biz_ids ])

def _biz_map(self, businesses): return { biz.id: biz for biz in businesses }

Page 50: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Dataloader class BusinessDataLoader(DataLoader):

def __init__(self, locale, **kwargs): super().__init__(**kwargs) self._locale = locale

def batch_load_fn(self, biz_ids): businesses = get_businesses_info( biz_ids, self._locale ).result()

biz_id_map = self._biz_map( businesses )

return Promise.resolve([ biz_id_map.get(biz_id) for biz_id in biz_ids ])

def _biz_map(self, businesses): return { biz.id: biz for biz in businesses }

Page 51: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

class Query(graphene.ObjectType):

# ...

@verify_limited_graphql_access('graphql') def resolve_business(root, args, context, info): alias = args.get('alias') internalapi_client = get_internal_api_client()

business = internalapi_client.business.get_business( business_alias=alias ).result()

return context['dataloaders'].businesses.load(business.id)

Page 52: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Our Setup• Dedicated public API service• Python 3.6• Graphene (Python GraphQL library)• Pyramid + uWSGI• Complex microservices architecture• No attached database

Page 53: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Considerations• Caching• Performance• Complexity• Rate limiting• Security• Error handling

Page 54: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Caching• Edge caching is hard• Greater diversity of requests• Many caching strategies don't fit

Page 55: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

query { business(id: "yelp") { name }}

Page 56: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

query { business(id: "yelp") { name rating }}

Page 57: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

query { search(term: "burrito", latitude: 30.000, longitude: 30.000) { ... }}

Page 58: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

query { search(term: "burrito", latitude: 30.001, longitude: 30.001) { ... }}

Page 59: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

query { search(term: "Burrito", latitude: 30.000, longitude: 30.000) { ... }}

Page 60: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Service Caching• Network caching proxy• Generic caching service• Can wrap any service, applies to everyone

Page 61: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Page 62: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Page 63: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Page 64: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

What about bulk data?

Page 65: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Caching in Bulk• ID-based caching, setup a key: value cache map• Parse and cache individual models, don't cache the entire response as-is

Page 66: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

cached_endpoints: user.v2: { ttl: 3600, pattern: "(^/user/v2(?:\\?|\\?.*&)ids=)((?:\\d|%2C)+)(&.*$|$)", bulk_support: true, id_identifier: 'id' }

Page 67: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

cached_endpoints: user.v2: { ttl: 3600, pattern: "(^/user/v2(?:\\?|\\?.*&)ids=)((?:\\d|%2C)+)(&.*$|$)", bulk_support: true, id_identifier: 'id' }

Page 68: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Request Budgets

Page 69: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Request BudgetsX-Ctx-Request-Budget 1000

sleep(0.470)

X-Ctx-Request-Budget 530

Page 70: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Request BudgetsX-Ctx-Request-Budget 1000

sleep(1.470)

X-Ctx-Request-Budget -530

Page 71: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Complexity

Page 72: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { reviews { users { reviews { business { categories } } } } }}

{ "data": { "business": { "reviews": [{ "users": [{ "reviews": [ { "business": { "categories": ["food"] } }, { "business": { "categories": ["media"] } } ] }] }] } } }

Page 73: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Page 74: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017
Page 75: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Rate Limiting

Page 76: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Normally GET https://api.yelp.com/v3/search

Page 77: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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

Page 78: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

GraphQL POST https://api.yelp.com/v3/graphql

query { business(id: "yelp-san-francisco") { name } }

Page 79: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

GraphQL POST https://api.yelp.com/v3/graphql

query { b1: business(id: "yelp-san-francisco") { name } b2: business(id: "garaje-san-francisco") { name } b3: business(id: "moma-san-francisco") { name } }

Page 80: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

GraphQL POST https://api.yelp.com/v3/graphql

query { search(term: "burrito", location: "sf") { business { name reviews { rating text } } } }

Page 81: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 } } } }

Page 82: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 } } } }

Page 83: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 } } } }

Page 84: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 } } } }

Page 85: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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 } } }

Page 86: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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" }, ... ] } } }

Page 87: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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" }, ... ] } } }

Page 88: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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

Page 89: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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

Page 90: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

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

Page 91: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Easy* failure handling and retries• GraphQL requests can partially succeed!

Page 92: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "yelp") { name rating reviews { text } hours { ... } }}

{ "data": { "business": { "name": "Yelp", "rating": 4.9, "reviews": [{ "text": "some review" }], "hours": null, } }, "errors": [ { "description": "could not load hours", "error_code": "HOURS_FAILED" } ] }

HTTP 200

Page 93: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Easy* failure handling and retries• GraphQL requests can partially succeed!• But… that makes some other failure cases trickier

Page 94: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ business(id: "123-fake-street") { name rating reviews { text } hours { ... } }}

{ "data": null, "errors": [ { "description": "business not found", "error_code": "BUSINESS_NOT_FOUND" } ] }

HTTP 200

Page 95: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

def talk(): return end

Page 96: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

Building UI Consistent Android AppsSaturday - 11.30 in Room 6 

Nicola Corti

Page 97: Tomer Elmalem - GraphQL APIs: REST in Peace - Codemotion Milan 2017

{ questions? { answers } }