SlideShare a Scribd company logo
How to grow GraphQL and
remove SQLAlchemy and REST
API from a high-load Python
project
Oleksandr Tarasenko
EVO.company / prom.ua
Some of prom.ua numbers
● RPS 2500-3500
● total sites over 300 000
● products ~ 150 million
● pages ~ 400 million
2
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
4
GraphQL
http://graphql.org/users/
5
http://graphql.org/users/ 6
Key History Notes
● 2008 - Start. SQLAlchemy
● 2014 - SQLConstruct
● 2014 - ElasticSearch ecosystem
● 2016 - GraphQL
● 2017 - Microservices way
● 2018 - Python 3.7 + async
7
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
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
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
2016
● hiku, https://hiku.readthedocs.io/
● two-level graph
● mobile API with DSL-QL (EDN)
11
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
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
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
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
2017
● hiku upgrade to native GraphQL
● new site 2nd level graph
● reimplementing mobile sites
● SSR Node.js Apollo Client
● success story with metrics
16
17
Frontend
18
Node.js
Node.js implementation
● Only for the first request (SSR)
● Good for React prerender
● Routing
● Two-step implementation
19
20
21
22
Success metrics
23
from 20.5% to 14.5%
Success metrics
24
from 57.5% to 49%
??? metrics
25
from 46 to 36
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
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
Early 2018
● new order and shopping cart graphs
● new user cabinet
● Node.js and apollo for stitching two
graphs
● REST API under GraphQL
28
GraphQL schema stitching
29https://labs.getninjas.com.br/sharing-data-in-a-microservices-architecture-using-graphql-97db59357602
How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Python project - Pycon Belarus 2019
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
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
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),
...
])
33
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),
...
])
34
product(ids: [1234, 1235]) {
id
name
price
}
"""
SELECT id, name, price FROM product
WHERE id IN (:id1, :id2, ..., :idN)
"""
35
product(ids: [1234, 1235]) {
id
name
price
}
"""
SELECT id, name, price FROM product
WHERE id IN (:id1, :id2, ..., :idN)
"""
36
low_level_graph = Graph([
Node('Product', [
Field('id', Integer, product_query),
...
Link(
'discount',
Optional[TypeRef['Discount']],
product_to_discount_query,
requires='id',
),
]),
Node('Discount', [
Field('id', Integer, discount_query),
Field('amount', Integer, discount_query),
])
])
37
low_level_graph = Graph([
Node('Product', [
Field('id', Integer, product_query),
...
Link(
'discount',
Optional[TypeRef['Discount']],
product_to_discount_query,
requires='id',
),
]),
Node('Discount', [
Field('id', Integer, discount_query),
Field('amount', Integer, discount_query),
])
])
38
low_level_graph = Graph([
Node('Product', [
Field('id', Integer, product_query),
...
Link(
'discount',
Optional[TypeRef['Discount']],
product_to_discount_query,
requires='id',
),
]),
Node('Discount', [
Field('id', Integer, discount_query),
Field('amount', Integer, discount_query),
])
])
39
40
discount_query = sa.FieldsQuery('db.session', Discount.__table__)
"""
SELECT id, name, price, price_currency_id FROM product
WHERE id IN (:id1, :id2, ..., :idN)
"""
product_to_discount_query = sa.LinkQuery(
'db.session',
from_column=Discount.product_id,
to_column=Discount.product_id,
)
"""
SELECT product_id FROM discount
WHERE product_id IN (:id1, :id2, ..., :idN)
"""
41
discount_query = sa.FieldsQuery('db.session', Discount.__table__)
"""
SELECT id, name, price, price_currency_id FROM product
WHERE id IN (:id1, :id2, ..., :idN)
"""
product_to_discount_query = sa.LinkQuery(
'db.session',
from_column=Discount.product_id,
to_column=Discount.product_id,
)
"""
SELECT product_id FROM discount
WHERE product_id IN (:id1, :id2, ..., :idN)
"""
42
product_sg = SubGraph(low_level_graph, 'Product')
high_level_graph = Graph([
Node('Product', [
Field('id', String, product_sg),
Field('name', String, product_sg.compile(S.this.name)),
Field('priceText', String, product_sg.compile(
get_price_text(S.this)
)),
Field('hasDiscount', Boolean, product_sg.compile(
is_discount_available(S.this, S.this.discount)
)),
Field('discountedPriceText', String, product_sg.compile(
get_discounted_price_text(S.this, S.this.discount)
)),
]),
])
43
product_sg = SubGraph(low_level_graph, 'Product')
high_level_graph = Graph([
Node('Product', [
Field('id', String, product_sg),
Field('name', String, product_sg.compile(S.this.name)),
Field('priceText', String, product_sg.compile(
get_price_text(S.this)
)),
Field('hasDiscount', Boolean, product_sg.compile(
is_discount_available(S.this, S.this.discount)
)),
Field('discountedPriceText', String, product_sg.compile(
get_discounted_price_text(S.this, S.this.discount)
)),
]),
])
44
product_sg = SubGraph(low_level_graph, 'Product')
high_level_graph = Graph([
Node('Product', [
Field('id', String, product_sg),
Field('name', String, product_sg.compile(S.this.name)),
Field('priceText', String, product_sg.compile(
get_price_text(S.this)
)),
Field('hasDiscount', Boolean, product_sg.compile(
is_discount_available(S.this, S.this.discount)
)),
Field('discountedPriceText', String, product_sg.compile(
get_discounted_price_text(S.this, S.this.discount)
)),
]),
])
45
@define(Record[{
'price': Float,
'price_currency_id': Integer,
'currency_settings': Any
}])
def get_price_text(product):
product_price_text = format_currency_data(
product_price,
product['price_currency_id'],
)
...
//..
return product_price_text.formatted_number
Key History Notes
● 2008 - Start. SqlAlchemy
● 2014 - SQL Construct
● 2014 - ElasticSearch ecosystem
● 2016 - GraphQL
● 2017 - Microservices way
● 2018 - Python 3.7 + async
46
47
Python 3.7 + Async + GraphQL
48
def link_to_some_product(opts):
product = db.session.query(
Product.id
).filter(
Product.id == opts['id'],
Product.status_on_display(),
).first()
if product is not None:
return product.id
else:
return Nothing
49
async def link_to_some_product(opts):
expr = select([Product.id]).where(
and_(
Product.id == opts['id'],
Product.status_on_display()
)
)
async with async_engine() as query_ctx:
product_id = await query_ctx.scalar(expr)
return product_id or Nothing
50
async def link_to_some_product(opts):
expr = select([Product.id]).where(
and_(
Product.id == opts['id'],
Product.status_on_display()
)
)
async with async_engine() as query_ctx:
product_id = await query_ctx.scalar(expr)
return product_id or Nothing
51
product_query = sa.FieldsQuery(
'db.session', Product.__table__)
product_query = asyncsa.FieldsQuery(
'db.session_async', Product.table)
52
product_query = sa.FieldsQuery(
'db.session', Product.__table__)
product_query = asyncsa.FieldsQuery(
'db.session_async', Product.__table__)
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
54
Example with aliases
def get_price_lists_data():
query = build([
Q.priceLists[
Q.id,
Q.name,
Q.file_id << Q.priceFileId,
Q.date_posted << Q.datePosted,
Q.url << Q.fileUrl,
]
])
graph_data = execute(query)
return graph_data.priceLists
55
Example with aliases
def get_price_lists_data():
query = build([
Q.priceLists[
Q.id,
Q.name,
Q.file_id << Q.priceFileId,
Q.date_posted << Q.datePosted,
Q.url << Q.fileUrl,
]
])
graph_data = execute(query)
return graph_data.priceLists
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
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
End of 2018 - now
● Hiku upgrade to mutations
● New 2nd level graph for opinions and
mobile cabinet API
● Read and write with graph
58
mutation_graph = Graph(repr_graph.nodes + [
Root([
Link(
'addNewOpinion',
TypeRef['NewCommentResult'],
add_new_comment_for_opinion,
options=[
Option('opinion_id', Integer),
Option('comment', String),
], requires=None,
),
]),
])
mutation_graph = Graph(repr_graph.nodes + [
Root([
Link(
'addNewOpinion',
TypeRef['NewCommentResult'],
add_new_comment_for_opinion,
options=[
Option('opinion_id', Integer),
Option('comment', String),
], requires=None,
),
]),
])
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=[])
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=[])
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=[])
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
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
New client-proposal paradigm
via GraphQL
GraphQL everywhere
Testing with hypothesis
Q/A
Hiku

More Related Content

ODP
java ee 6 Petcatalog
PDF
Introduction to interactive data visualisation using R Shiny
PDF
ASP.Net 3.5 SP1 Dynamic Data
PDF
iOS Reactive Cocoa Pipeline
PDF
Salesforce meetup | Custom document generation
PDF
Getting Started with GraphQL && PHP
PDF
Salesforce meetup | Lightning Web Component
PDF
Tutorial: Building a GraphQL API in PHP
java ee 6 Petcatalog
Introduction to interactive data visualisation using R Shiny
ASP.Net 3.5 SP1 Dynamic Data
iOS Reactive Cocoa Pipeline
Salesforce meetup | Custom document generation
Getting Started with GraphQL && PHP
Salesforce meetup | Lightning Web Component
Tutorial: Building a GraphQL API in PHP

What's hot (8)

PDF
Building interactive web app with shiny
PDF
Recoil at Codete Webinars #3
PPTX
Building a GraphQL API in PHP
PPTX
Smarter data analysis with JavaScript and Azure ML functions in Excel
PDF
Performant APIs with GraphQL and PHP (Dutch PHP 2019)
PDF
Charting with Google
PPTX
Google Chart Tools Kanika Garg (10BM60035) Lavanya R. (10BM60042)
PDF
Integrating React.js Into a PHP Application: Dutch PHP 2019
Building interactive web app with shiny
Recoil at Codete Webinars #3
Building a GraphQL API in PHP
Smarter data analysis with JavaScript and Azure ML functions in Excel
Performant APIs with GraphQL and PHP (Dutch PHP 2019)
Charting with Google
Google Chart Tools Kanika Garg (10BM60035) Lavanya R. (10BM60042)
Integrating React.js Into a PHP Application: Dutch PHP 2019
Ad

Similar to How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Python project - Pycon Belarus 2019 (20)

PDF
How to separate frontend from a highload python project with no problems - Py...
PDF
apidays LIVE Paris - Augmenting a Legacy REST API with GraphQL by Clément Vil...
DOCX
GraphQL Advanced Concepts A Comprehensive Guide.docx
PPTX
Introduction to GraphQL
PDF
Graphql
PDF
Intro to GraphQL
PDF
apidays LIVE Paris - GraphQL meshes by Jens Neuse
PDF
Discover GraphQL with Python, Graphene and Odoo
DOCX
How to Deploy a GraphQL API A Comprehensive Guide.docx
PDF
GraphQL the holy contract between client and server
PDF
Adding GraphQL to your existing architecture
PDF
GraphQL across the stack: How everything fits together
PDF
GraphQL Isn't An Excuse To Stop Writing Docs
PDF
GraphQL Munich Meetup #1 - How We Use GraphQL At Commercetools
PDF
apidays LIVE Hong Kong 2021 - GraphQL : Beyond APIs, graph your enterprise by...
PPTX
Green Custard Friday Talk 8: GraphQL
DOCX
Graphql for Frontend Developers Simplifying Data Fetching.docx
PDF
Scaling Your Team With GraphQL: Why Relationships Matter
PDF
GraphQL Bangkok meetup 5.0
PPTX
GraphQL - an elegant weapon... for more civilized age
How to separate frontend from a highload python project with no problems - Py...
apidays LIVE Paris - Augmenting a Legacy REST API with GraphQL by Clément Vil...
GraphQL Advanced Concepts A Comprehensive Guide.docx
Introduction to GraphQL
Graphql
Intro to GraphQL
apidays LIVE Paris - GraphQL meshes by Jens Neuse
Discover GraphQL with Python, Graphene and Odoo
How to Deploy a GraphQL API A Comprehensive Guide.docx
GraphQL the holy contract between client and server
Adding GraphQL to your existing architecture
GraphQL across the stack: How everything fits together
GraphQL Isn't An Excuse To Stop Writing Docs
GraphQL Munich Meetup #1 - How We Use GraphQL At Commercetools
apidays LIVE Hong Kong 2021 - GraphQL : Beyond APIs, graph your enterprise by...
Green Custard Friday Talk 8: GraphQL
Graphql for Frontend Developers Simplifying Data Fetching.docx
Scaling Your Team With GraphQL: Why Relationships Matter
GraphQL Bangkok meetup 5.0
GraphQL - an elegant weapon... for more civilized age
Ad

Recently uploaded (20)

PPTX
Lecture Notes Electrical Wiring System Components
PDF
Mohammad Mahdi Farshadian CV - Prospective PhD Student 2026
PPTX
UNIT-1 - COAL BASED THERMAL POWER PLANTS
PPTX
Infosys Presentation by1.Riyan Bagwan 2.Samadhan Naiknavare 3.Gaurav Shinde 4...
PPTX
CYBER-CRIMES AND SECURITY A guide to understanding
PPTX
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
PPTX
MET 305 2019 SCHEME MODULE 2 COMPLETE.pptx
PPT
CRASH COURSE IN ALTERNATIVE PLUMBING CLASS
PDF
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
PDF
Operating System & Kernel Study Guide-1 - converted.pdf
PPTX
Engineering Ethics, Safety and Environment [Autosaved] (1).pptx
PPTX
CH1 Production IntroductoryConcepts.pptx
PDF
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
PDF
SM_6th-Sem__Cse_Internet-of-Things.pdf IOT
PDF
PRIZ Academy - 9 Windows Thinking Where to Invest Today to Win Tomorrow.pdf
PPT
Mechanical Engineering MATERIALS Selection
PPTX
UNIT 4 Total Quality Management .pptx
PPTX
CARTOGRAPHY AND GEOINFORMATION VISUALIZATION chapter1 NPTE (2).pptx
PDF
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
DOCX
573137875-Attendance-Management-System-original
Lecture Notes Electrical Wiring System Components
Mohammad Mahdi Farshadian CV - Prospective PhD Student 2026
UNIT-1 - COAL BASED THERMAL POWER PLANTS
Infosys Presentation by1.Riyan Bagwan 2.Samadhan Naiknavare 3.Gaurav Shinde 4...
CYBER-CRIMES AND SECURITY A guide to understanding
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
MET 305 2019 SCHEME MODULE 2 COMPLETE.pptx
CRASH COURSE IN ALTERNATIVE PLUMBING CLASS
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
Operating System & Kernel Study Guide-1 - converted.pdf
Engineering Ethics, Safety and Environment [Autosaved] (1).pptx
CH1 Production IntroductoryConcepts.pptx
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
SM_6th-Sem__Cse_Internet-of-Things.pdf IOT
PRIZ Academy - 9 Windows Thinking Where to Invest Today to Win Tomorrow.pdf
Mechanical Engineering MATERIALS Selection
UNIT 4 Total Quality Management .pptx
CARTOGRAPHY AND GEOINFORMATION VISUALIZATION chapter1 NPTE (2).pptx
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
573137875-Attendance-Management-System-original

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
  • 7. Key History Notes ● 2008 - Start. SQLAlchemy ● 2014 - SQLConstruct ● 2014 - ElasticSearch ecosystem ● 2016 - GraphQL ● 2017 - Microservices way ● 2018 - Python 3.7 + async 7
  • 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
  • 11. 2016 ● hiku, https://hiku.readthedocs.io/ ● two-level graph ● mobile API with DSL-QL (EDN) 11
  • 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
  • 19. Node.js implementation ● Only for the first request (SSR) ● Good for React prerender ● Routing ● Two-step implementation 19
  • 20. 20
  • 21. 21
  • 22. 22
  • 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
  • 33. 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), ... ]) 33
  • 34. 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), ... ]) 34
  • 35. product(ids: [1234, 1235]) { id name price } """ SELECT id, name, price FROM product WHERE id IN (:id1, :id2, ..., :idN) """ 35
  • 36. product(ids: [1234, 1235]) { id name price } """ SELECT id, name, price FROM product WHERE id IN (:id1, :id2, ..., :idN) """ 36
  • 37. low_level_graph = Graph([ Node('Product', [ Field('id', Integer, product_query), ... Link( 'discount', Optional[TypeRef['Discount']], product_to_discount_query, requires='id', ), ]), Node('Discount', [ Field('id', Integer, discount_query), Field('amount', Integer, discount_query), ]) ]) 37
  • 38. low_level_graph = Graph([ Node('Product', [ Field('id', Integer, product_query), ... Link( 'discount', Optional[TypeRef['Discount']], product_to_discount_query, requires='id', ), ]), Node('Discount', [ Field('id', Integer, discount_query), Field('amount', Integer, discount_query), ]) ]) 38
  • 39. low_level_graph = Graph([ Node('Product', [ Field('id', Integer, product_query), ... Link( 'discount', Optional[TypeRef['Discount']], product_to_discount_query, requires='id', ), ]), Node('Discount', [ Field('id', Integer, discount_query), Field('amount', Integer, discount_query), ]) ]) 39
  • 40. 40 discount_query = sa.FieldsQuery('db.session', Discount.__table__) """ SELECT id, name, price, price_currency_id FROM product WHERE id IN (:id1, :id2, ..., :idN) """ product_to_discount_query = sa.LinkQuery( 'db.session', from_column=Discount.product_id, to_column=Discount.product_id, ) """ SELECT product_id FROM discount WHERE product_id IN (:id1, :id2, ..., :idN) """
  • 41. 41 discount_query = sa.FieldsQuery('db.session', Discount.__table__) """ SELECT id, name, price, price_currency_id FROM product WHERE id IN (:id1, :id2, ..., :idN) """ product_to_discount_query = sa.LinkQuery( 'db.session', from_column=Discount.product_id, to_column=Discount.product_id, ) """ SELECT product_id FROM discount WHERE product_id IN (:id1, :id2, ..., :idN) """
  • 42. 42 product_sg = SubGraph(low_level_graph, 'Product') high_level_graph = Graph([ Node('Product', [ Field('id', String, product_sg), Field('name', String, product_sg.compile(S.this.name)), Field('priceText', String, product_sg.compile( get_price_text(S.this) )), Field('hasDiscount', Boolean, product_sg.compile( is_discount_available(S.this, S.this.discount) )), Field('discountedPriceText', String, product_sg.compile( get_discounted_price_text(S.this, S.this.discount) )), ]), ])
  • 43. 43 product_sg = SubGraph(low_level_graph, 'Product') high_level_graph = Graph([ Node('Product', [ Field('id', String, product_sg), Field('name', String, product_sg.compile(S.this.name)), Field('priceText', String, product_sg.compile( get_price_text(S.this) )), Field('hasDiscount', Boolean, product_sg.compile( is_discount_available(S.this, S.this.discount) )), Field('discountedPriceText', String, product_sg.compile( get_discounted_price_text(S.this, S.this.discount) )), ]), ])
  • 44. 44 product_sg = SubGraph(low_level_graph, 'Product') high_level_graph = Graph([ Node('Product', [ Field('id', String, product_sg), Field('name', String, product_sg.compile(S.this.name)), Field('priceText', String, product_sg.compile( get_price_text(S.this) )), Field('hasDiscount', Boolean, product_sg.compile( is_discount_available(S.this, S.this.discount) )), Field('discountedPriceText', String, product_sg.compile( get_discounted_price_text(S.this, S.this.discount) )), ]), ])
  • 45. 45 @define(Record[{ 'price': Float, 'price_currency_id': Integer, 'currency_settings': Any }]) def get_price_text(product): product_price_text = format_currency_data( product_price, product['price_currency_id'], ) ... //.. return product_price_text.formatted_number
  • 46. Key History Notes ● 2008 - Start. SqlAlchemy ● 2014 - SQL Construct ● 2014 - ElasticSearch ecosystem ● 2016 - GraphQL ● 2017 - Microservices way ● 2018 - Python 3.7 + async 46
  • 47. 47 Python 3.7 + Async + GraphQL
  • 48. 48 def link_to_some_product(opts): product = db.session.query( Product.id ).filter( Product.id == opts['id'], Product.status_on_display(), ).first() if product is not None: return product.id else: return Nothing
  • 49. 49 async def link_to_some_product(opts): expr = select([Product.id]).where( and_( Product.id == opts['id'], Product.status_on_display() ) ) async with async_engine() as query_ctx: product_id = await query_ctx.scalar(expr) return product_id or Nothing
  • 50. 50 async def link_to_some_product(opts): expr = select([Product.id]).where( and_( Product.id == opts['id'], Product.status_on_display() ) ) async with async_engine() as query_ctx: product_id = await query_ctx.scalar(expr) return product_id or Nothing
  • 51. 51 product_query = sa.FieldsQuery( 'db.session', Product.__table__) product_query = asyncsa.FieldsQuery( 'db.session_async', Product.table)
  • 52. 52 product_query = sa.FieldsQuery( 'db.session', Product.__table__) product_query = asyncsa.FieldsQuery( 'db.session_async', Product.__table__)
  • 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
  • 54. 54 Example with aliases def get_price_lists_data(): query = build([ Q.priceLists[ Q.id, Q.name, Q.file_id << Q.priceFileId, Q.date_posted << Q.datePosted, Q.url << Q.fileUrl, ] ]) graph_data = execute(query) return graph_data.priceLists
  • 55. 55 Example with aliases def get_price_lists_data(): query = build([ Q.priceLists[ Q.id, Q.name, Q.file_id << Q.priceFileId, Q.date_posted << Q.datePosted, Q.url << Q.fileUrl, ] ]) graph_data = execute(query) return graph_data.priceLists
  • 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
  • 59. mutation_graph = Graph(repr_graph.nodes + [ Root([ Link( 'addNewOpinion', TypeRef['NewCommentResult'], add_new_comment_for_opinion, options=[ Option('opinion_id', Integer), Option('comment', String), ], requires=None, ), ]), ])
  • 60. mutation_graph = Graph(repr_graph.nodes + [ Root([ Link( 'addNewOpinion', TypeRef['NewCommentResult'], add_new_comment_for_opinion, options=[ Option('opinion_id', Integer), Option('comment', String), ], requires=None, ), ]), ])
  • 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