GraphQL is a new black, a hype over the Internet with a very few real-life examples of how to use it in big in-house projects. I’d like to show the real example of GraphQL Evolution from a small mobile API to a cross-services integration in a high-load Python project that took us 3 years to develop.
Description:
Intro to GraphQL in the Python world.
Step-by-step GraphQL evolution in a big high-load python project:
Step 1. Mobile App API with GraphQL
Step 2. Separate Frontend from Backend in high-load python project using GraphQL
Step 3. Graph services as Proxy via different Graph APIs
Step 4. Replace SqlAlchemy models via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL
For every step, I will provide real examples (metrics, graphics, numbers), problems and solutions that we had during the years 2015 - 2019.
Query optimization and processing for advanced database systems
How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Python project - Pycon Belarus 2019
1. How to grow GraphQL and
remove SQLAlchemy and REST
API from a high-load Python
project
Oleksandr Tarasenko
EVO.company / prom.ua
2. Some of prom.ua numbers
● RPS 2500-3500
● total sites over 300 000
● products ~ 150 million
● pages ~ 400 million
2
3. What we had
● monolithic WSGI app (11 years)
● mako templates
● monolithic database
● monolithic Nginx
● poor REST API for all sub-services
● slow Delivery and Deployment
● new features were hard to develop
3
8. GraphQL concept in prom.ua
● new client-proposal paradigm
● good for read-only requests and data
● two-level (x-level) graph:
○ low-level graph for database mapping (data
loading)
○ high-level graph for business logic
● auto documentation and easy testing with
graphiql tool
● data validation 8
9. 9
Step 1. Mobile App API with GraphQL
Step 2. Separating Frontend from Backend
Step 3. Graph services as Proxy via different Graph APIs
Step 4. Replace SQLAlchemy models logic via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL
10. Why we choose GraphQL
● good for read-only requests and data
● no mutations needed
● hard to update REST API versions (v1, v2,...vn)
● auto generated documentation
● new client-proposal paradigm
● difficult routes
10
12. from hiku.graph import Graph, Node, Link, Field, ...
from hiku.sources import sqlalchemy as sa
from hiku.types import Boolean, Integer, String, TypeRef, ...
product_query = sa.FieldsQuery('db.session', Product.__table__)
low_level_graph = Graph([
Node('Product', [
Field('id', Integer, product_query),
Field('name', String, product_query),
Field('price', None, product_query),
...
])
12
13. from hiku.graph import Graph, Node, Link, Field
from hiku.sources import sqlalchemy as sa
from hiku.types import Boolean, Integer, String, TypeRef
product_query = sa.FieldsQuery('db.session', Product.__table__)
low_level_graph = Graph([
Node('Product', [
Field('id', Integer, product_query),
Field('name', String, product_query),
Field('price', None, product_query),
...
])
13
14. from hiku.graph import Graph, Node, Link, Field
from hiku.sources import sqlalchemy as sa
from hiku.types import Boolean, Integer, String, TypeRef
product_query = sa.FieldsQuery('db.session', Product.__table__)
low_level_graph = Graph([
Node('Product', [
Field('id', Integer, product_query),
Field('name', String, product_query),
Field('price', None, product_query),
...
])
14
15. 15
Step 1. Mobile App API with GraphQL
Step 2. Separating Frontend from Backend
Step 3. Graph services as Proxy via different Graph APIs
Step 4. Replace SQLAlchemy models logic via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL
16. 2017
● hiku upgrade to native GraphQL
● new site 2nd level graph
● reimplementing mobile sites
● SSR Node.js Apollo Client
● success story with metrics
16
26. Some of the numbers of the new scheme
● node.js workers 3-6 per front
● React render 5 ms
● prom.ua workers over 750
● number of requests split to node/python
26
27. 27
Step 1. Mobile App API with GraphQL
Step 2. Separating Frontend from Backend
Step 3. Graph services as Proxy via
different Graph APIs
Step 4. Replace SQLAlchemy models logic via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL
28. Early 2018
● new order and shopping cart graphs
● new user cabinet
● Node.js and apollo for stitching two
graphs
● REST API under GraphQL
28
31. 31
Step 1. Mobile App API with GraphQL
Step 2. Separating Frontend from Backend
Step 3. Graph services as Proxy via different Graph APIs
Step 4. Replace SQLAlchemy models
logic via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL
32. Middle 2018
● Hiku upgrade to aliases and new data types
● Use SSR + GraphQL for portal mobile version
● Graph for replace SQLAlchemy models logic
and queries
● Rewriting sites SQL queries to GraphQL
● Remove Models from BL
● https://hiku.readthedocs.io/
32
53. Mobile API average across all queries:
383 ms -> 323 ms 15%
Catalog Graph API average across all queries:
82 ms -> 62 ms 25%
Site Graph Api average across all queries
121 ms -> 108 ms 11%
Async + GraphQL results
56. A few facts of prom.ua graphs
● number of graphs: ~ 20
● number of fields: ~ 2000
● number of links: ~ 300
● number of nodes: ~ 250
● single entry point /graphql
● refactoring and new vision for code
● better monitoring
● easy to test API with graphiql
56
Hiku
57. 57
Step 1. Mobile App API with GraphQL
Step 2. Separating Frontend from Backend
Step 3. Graph services as Proxy via different Graph APIs
Step 4. Replace SQLAlchemy models logic via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL
58. End of 2018 - now
● Hiku upgrade to mutations
● New 2nd level graph for opinions and
mobile cabinet API
● Read and write with graph
58
61. def add_new_comment_for_opinion(ctx, options):
opinion_id = options['opinion_id']
comment = options['comment']
user = ctx['user']
opinion = Opinion.get(opinion_id)
...
form = OpinionCommentForm()
if not form.validate():
return NewCommentResult(id=None, errors=form.validate_resp())
comment = opinion.new_comment(...)
comment.message = clear_text(form.data['comment'])
comment.author_user_id = user.id
db.session.commit()
return NewCommentResult(id=comment.id, errors=[])
62. def add_new_comment_for_opinion(ctx, options):
opinion_id = options['opinion_id']
comment = options['comment']
user = ctx['user']
opinion = Opinion.get(opinion_id)
...
form = OpinionCommentForm()
if not form.validate():
return NewCommentResult(id=None, errors=form.validate_resp())
comment = opinion.new_comment(...)
comment.message = clear_text(form.data['comment'])
comment.author_user_id = user.id
db.session.commit()
return NewCommentResult(id=comment.id, errors=[])
63. def add_new_comment_for_opinion(ctx, options):
opinion_id = options['opinion_id']
comment = options['comment']
user = ctx['user']
opinion = Opinion.get(opinion_id)
...
form = OpinionCommentForm()
if not form.validate():
return NewCommentResult(id=None, errors=form.validate_resp())
comment = opinion.new_comment(...)
comment.message = clear_text(form.data['comment'])
comment.author_user_id = user.id
db.session.commit()
return NewCommentResult(id=comment.id, errors=[])
64. def add_new_comment_for_opinion(ctx, options):
opinion_id = options['opinion_id']
comment = options['comment']
user = ctx['user']
opinion = Opinion.get(opinion_id)
...
form = OpinionCommentForm()
if not form.validate():
return NewCommentResult(id=None, errors=form.validate_resp())
comment = opinion.new_comment(...)
comment.message = clear_text(form.data['comment'])
comment.author_user_id = user.id
db.session.commit()
return NewCommentResult(id=comment.id, errors=[])
65. 65
Step 1. Mobile App API with GraphQL
Step 2. Separating Frontend from Backend
Step 3. Graph services as Proxy via different Graph APIs
Step 4. Replace SQLAlchemy models logic via Graph
Step 5. Mutations in Graph API
Step 6. A brave new world with GraphQL