SlideShare ist ein Scribd-Unternehmen logo
1 von 45
Downloaden Sie, um offline zu lesen
How to separate frontend
from a highload python
project with no problems
Oleksandr Tarasenko
EVO.company / prom.ua
Some prom.ua numbers
● RPS 2000-3000
● users over 2.5 million per day
● paid sites ~ 50 000
● total sites over 300 000
● products ~ 100 million
● pages ~ 300 million
2
What we had
● monolithic WSGI app (10 years)
● mako templates
● monolithic database
● monolithic Nginx
● poor REST API
● slow Delivery and Deployment
● new features were hard to develop
3
4
1. Resolve url
2. Collect base context
3. Call render_mako
4. Collect template context
5. Return html
Our goals
● SEO
● Independent frontend
● Good user experience
● Documentation
● Data validation
● Microservices way
● Independent CI/CD
5
Tools and expertise
● ReactJS / Redux
● GraphQL server (hiku)
● Container system (vagga/lithos)
● Metrics (grafana)
● Logs (kibana)
6
GraphQL + React + Apollo + Node.js =
success story
7
8
GraphQL
http://graphql.org/users/
9
Why we choose GraphQL
● good for readonly requests and data
● mutations to REST
● every site has unique user settings
● different templates
10
Some prom.ua sites code facts
● a lot of views and url endpoints
● almost no documentation
● poor versioning
● a lot of template context
● duplicating queries
11
We built two-level graph
● low-level graph for database mapping
(data loading)
● high-level graph for business logic
● auto documentation with graphiql tool
● data validation
12
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
"""
SELECT id, name, price FROM product
WHERE id IN (:id1, :id2, ..., :idN)
"""
product(ids: [1234, 1235]) {
id
name
price
}
14
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),
])
])
15
16
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)
"""
17
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)
)),
]),
])
18
@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
Some facts of the new graph
● number of fields: ~ 500
● number of links: ~ 125
● number of nodes: ~ 75
● single entry point /graphql
● refactoring and new vision for code
● better monitoring
● easy to test API with graphiql
19
Hiku
20
Frontend
Stateless ReactJS
● React Apollo
● React Router
● React Helmet
● No Redux/Flux/State management lib
21
Apollo client benefits
● great GraphQL implementation
● no sugar in React components
● query batching
● state cache
● community
● combine | join graphs (Apollo server)
22
23
export const Article = ({ data }) => {
if (data.loading) return <Spinner />;
if (!data.article.id) return <NotFound />;
return (
<BasePage>
<ArticleViewMeta id={data.article.id} />
<BaseViewComponent
{...data.article}
/>
<Footer />
</BasePage>
);
};
24
const articleViewQuery = gql`
query getArticleViewData($id: String) {
article(id: $id){
id
title
text
}
}`;
export const ArticleWithData =
graphql(articleViewQuery, {
options: ({ id }) => {
return { variables: { id } };
},
})(Article);
Other javascript implementations
● Relay modern / Relay classic
● Apollo client / Apollo server / Apollo engine
● Lokka
● Prisma
● GraphQL.js
● express-graphql
25
26
Node.js
Node.js implementation
● Only for the first request (SSR)
● Good for React prerender
● Routing
● Two-step implementation
27
28
29
30
31
Nginx
32
33
Success product metrics
34
from 4.8 to 7.1
Success product metrics
35
from 57.5% to 49%
Some numbers of the new scheme
● node.js workers 8
● React render 5 ms
● prom.ua workers over 750
● number of requests split to node/python
36
37
Problems
38
Node.js leaks
39
Node.js leaks
:)
40
Node.js leaks
;(
Sometimes it’s hard to analyze js
errors from graph and Apollo
41
42
Hard to split routing
in monolithic app
43
Some resources and
logic need duplication
Plans
● GraphQL everywhere
● Backend as API
● Progressive web apps
● More frontends
44
45
GraphQL
REST API
Questions/Answers

Weitere ähnliche Inhalte

Was ist angesagt?

Was ist angesagt? (13)

Browses
BrowsesBrowses
Browses
 
Event-Driven Systems With MongoDB
Event-Driven Systems With MongoDBEvent-Driven Systems With MongoDB
Event-Driven Systems With MongoDB
 
Goa tutorial
Goa tutorialGoa tutorial
Goa tutorial
 
Workshop 20140522 BigQuery Implementation
Workshop 20140522   BigQuery ImplementationWorkshop 20140522   BigQuery Implementation
Workshop 20140522 BigQuery Implementation
 
Grails66 web service
Grails66 web serviceGrails66 web service
Grails66 web service
 
Grails Views
Grails ViewsGrails Views
Grails Views
 
JavaScript client API for Google Apps Script API primer
JavaScript client API for Google Apps Script API primerJavaScript client API for Google Apps Script API primer
JavaScript client API for Google Apps Script API primer
 
2016 foss4 g track: developing and implementing spatial etl processes with...
2016 foss4 g track:  developing and implementing  spatial etl processes  with...2016 foss4 g track:  developing and implementing  spatial etl processes  with...
2016 foss4 g track: developing and implementing spatial etl processes with...
 
JavaScript Patterns to Cleanup your Code
JavaScript Patterns to Cleanup your CodeJavaScript Patterns to Cleanup your Code
JavaScript Patterns to Cleanup your Code
 
Ken 20150306 心得分享
Ken 20150306 心得分享Ken 20150306 心得分享
Ken 20150306 心得分享
 
MongoDB.local Paris Keynote
MongoDB.local Paris KeynoteMongoDB.local Paris Keynote
MongoDB.local Paris Keynote
 
From CRUD to messages: a true story
From CRUD to messages: a true storyFrom CRUD to messages: a true story
From CRUD to messages: a true story
 
MongoDB - javascript for your data
MongoDB - javascript for your dataMongoDB - javascript for your data
MongoDB - javascript for your data
 

Ähnlich wie How to separate frontend from a highload python project with no problems - Pycon UA 2018

Ähnlich wie How to separate frontend from a highload python project with no problems - Pycon UA 2018 (20)

How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Pytho...
How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Pytho...How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Pytho...
How to grow GraphQL and remove SQLAlchemy and REST API from a high-load Pytho...
 
GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0GraphQL Bangkok Meetup 2.0
GraphQL Bangkok Meetup 2.0
 
Android Jetpack - Google IO Extended Singapore 2018
Android Jetpack - Google IO Extended Singapore 2018Android Jetpack - Google IO Extended Singapore 2018
Android Jetpack - Google IO Extended Singapore 2018
 
Angular Restmod (english version)
Angular Restmod (english version)Angular Restmod (english version)
Angular Restmod (english version)
 
RMLL 2013 - Synchronize OpenLDAP and Active Directory with LSC
RMLL 2013 - Synchronize OpenLDAP and Active Directory with LSCRMLL 2013 - Synchronize OpenLDAP and Active Directory with LSC
RMLL 2013 - Synchronize OpenLDAP and Active Directory with LSC
 
Simplify Access to Data from Pivotal GemFire Using the GraphQL (G2QL) Extension
Simplify Access to Data from Pivotal GemFire Using the GraphQL (G2QL) ExtensionSimplify Access to Data from Pivotal GemFire Using the GraphQL (G2QL) Extension
Simplify Access to Data from Pivotal GemFire Using the GraphQL (G2QL) Extension
 
Revealing ALLSTOCKER
Revealing ALLSTOCKERRevealing ALLSTOCKER
Revealing ALLSTOCKER
 
Pycon2011
Pycon2011Pycon2011
Pycon2011
 
Netflix Machine Learning Infra for Recommendations - 2018
Netflix Machine Learning Infra for Recommendations - 2018Netflix Machine Learning Infra for Recommendations - 2018
Netflix Machine Learning Infra for Recommendations - 2018
 
ML Infra for Netflix Recommendations - AI NEXTCon talk
ML Infra for Netflix Recommendations - AI NEXTCon talkML Infra for Netflix Recommendations - AI NEXTCon talk
ML Infra for Netflix Recommendations - AI NEXTCon talk
 
A head start on cloud native event driven applications - bigdatadays
A head start on cloud native event driven applications - bigdatadaysA head start on cloud native event driven applications - bigdatadays
A head start on cloud native event driven applications - bigdatadays
 
Graph computation
Graph computationGraph computation
Graph computation
 
Practical Model View Programming (Roadshow Version)
Practical Model View Programming (Roadshow Version)Practical Model View Programming (Roadshow Version)
Practical Model View Programming (Roadshow Version)
 
Sorry - How Bieber broke Google Cloud at Spotify
Sorry - How Bieber broke Google Cloud at SpotifySorry - How Bieber broke Google Cloud at Spotify
Sorry - How Bieber broke Google Cloud at Spotify
 
GraphQL the holy contract between client and server
GraphQL the holy contract between client and serverGraphQL the holy contract between client and server
GraphQL the holy contract between client and server
 
Go react codelab
Go react codelabGo react codelab
Go react codelab
 
Core2 Document - Java SCORE Overview.pptx.pdf
Core2 Document - Java SCORE Overview.pptx.pdfCore2 Document - Java SCORE Overview.pptx.pdf
Core2 Document - Java SCORE Overview.pptx.pdf
 
OrientDB - The 2nd generation of (multi-model) NoSQL
OrientDB - The 2nd generation of  (multi-model) NoSQLOrientDB - The 2nd generation of  (multi-model) NoSQL
OrientDB - The 2nd generation of (multi-model) NoSQL
 
Recoil at Codete Webinars #3
Recoil at Codete Webinars #3Recoil at Codete Webinars #3
Recoil at Codete Webinars #3
 
SF Big Analytics 20191112: How to performance-tune Spark applications in larg...
SF Big Analytics 20191112: How to performance-tune Spark applications in larg...SF Big Analytics 20191112: How to performance-tune Spark applications in larg...
SF Big Analytics 20191112: How to performance-tune Spark applications in larg...
 

Kürzlich hochgeladen

Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak HamilCara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Kandungan 087776558899
 
AKTU Computer Networks notes --- Unit 3.pdf
AKTU Computer Networks notes ---  Unit 3.pdfAKTU Computer Networks notes ---  Unit 3.pdf
AKTU Computer Networks notes --- Unit 3.pdf
ankushspencer015
 
Call Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
dharasingh5698
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night StandCall Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
amitlee9823
 

Kürzlich hochgeladen (20)

Booking open Available Pune Call Girls Koregaon Park 6297143586 Call Hot Ind...
Booking open Available Pune Call Girls Koregaon Park  6297143586 Call Hot Ind...Booking open Available Pune Call Girls Koregaon Park  6297143586 Call Hot Ind...
Booking open Available Pune Call Girls Koregaon Park 6297143586 Call Hot Ind...
 
Block diagram reduction techniques in control systems.ppt
Block diagram reduction techniques in control systems.pptBlock diagram reduction techniques in control systems.ppt
Block diagram reduction techniques in control systems.ppt
 
FEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced Loads
FEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced LoadsFEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced Loads
FEA Based Level 3 Assessment of Deformed Tanks with Fluid Induced Loads
 
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete RecordCCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
 
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak HamilCara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
 
University management System project report..pdf
University management System project report..pdfUniversity management System project report..pdf
University management System project report..pdf
 
AKTU Computer Networks notes --- Unit 3.pdf
AKTU Computer Networks notes ---  Unit 3.pdfAKTU Computer Networks notes ---  Unit 3.pdf
AKTU Computer Networks notes --- Unit 3.pdf
 
Call Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Netaji Nagar, Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
 
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
 
Intro To Electric Vehicles PDF Notes.pdf
Intro To Electric Vehicles PDF Notes.pdfIntro To Electric Vehicles PDF Notes.pdf
Intro To Electric Vehicles PDF Notes.pdf
 
Double Revolving field theory-how the rotor develops torque
Double Revolving field theory-how the rotor develops torqueDouble Revolving field theory-how the rotor develops torque
Double Revolving field theory-how the rotor develops torque
 
data_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdfdata_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdf
 
Double rodded leveling 1 pdf activity 01
Double rodded leveling 1 pdf activity 01Double rodded leveling 1 pdf activity 01
Double rodded leveling 1 pdf activity 01
 
UNIT - IV - Air Compressors and its Performance
UNIT - IV - Air Compressors and its PerformanceUNIT - IV - Air Compressors and its Performance
UNIT - IV - Air Compressors and its Performance
 
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 BookingVIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
VIP Call Girls Ankleshwar 7001035870 Whatsapp Number, 24/07 Booking
 
Online banking management system project.pdf
Online banking management system project.pdfOnline banking management system project.pdf
Online banking management system project.pdf
 
NFPA 5000 2024 standard .
NFPA 5000 2024 standard                                  .NFPA 5000 2024 standard                                  .
NFPA 5000 2024 standard .
 
Unit 1 - Soil Classification and Compaction.pdf
Unit 1 - Soil Classification and Compaction.pdfUnit 1 - Soil Classification and Compaction.pdf
Unit 1 - Soil Classification and Compaction.pdf
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
 
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night StandCall Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
 

How to separate frontend from a highload python project with no problems - Pycon UA 2018

  • 1. How to separate frontend from a highload python project with no problems Oleksandr Tarasenko EVO.company / prom.ua
  • 2. Some prom.ua numbers ● RPS 2000-3000 ● users over 2.5 million per day ● paid sites ~ 50 000 ● total sites over 300 000 ● products ~ 100 million ● pages ~ 300 million 2
  • 3. What we had ● monolithic WSGI app (10 years) ● mako templates ● monolithic database ● monolithic Nginx ● poor REST API ● slow Delivery and Deployment ● new features were hard to develop 3
  • 4. 4 1. Resolve url 2. Collect base context 3. Call render_mako 4. Collect template context 5. Return html
  • 5. Our goals ● SEO ● Independent frontend ● Good user experience ● Documentation ● Data validation ● Microservices way ● Independent CI/CD 5
  • 6. Tools and expertise ● ReactJS / Redux ● GraphQL server (hiku) ● Container system (vagga/lithos) ● Metrics (grafana) ● Logs (kibana) 6
  • 7. GraphQL + React + Apollo + Node.js = success story 7
  • 10. Why we choose GraphQL ● good for readonly requests and data ● mutations to REST ● every site has unique user settings ● different templates 10
  • 11. Some prom.ua sites code facts ● a lot of views and url endpoints ● almost no documentation ● poor versioning ● a lot of template context ● duplicating queries 11
  • 12. We built two-level graph ● low-level graph for database mapping (data loading) ● high-level graph for business logic ● auto documentation with graphiql tool ● data validation 12
  • 13. 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. """ SELECT id, name, price FROM product WHERE id IN (:id1, :id2, ..., :idN) """ product(ids: [1234, 1235]) { id name price } 14
  • 15. 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), ]) ]) 15
  • 16. 16 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) """
  • 17. 17 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) )), ]), ])
  • 18. 18 @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
  • 19. Some facts of the new graph ● number of fields: ~ 500 ● number of links: ~ 125 ● number of nodes: ~ 75 ● single entry point /graphql ● refactoring and new vision for code ● better monitoring ● easy to test API with graphiql 19 Hiku
  • 21. Stateless ReactJS ● React Apollo ● React Router ● React Helmet ● No Redux/Flux/State management lib 21
  • 22. Apollo client benefits ● great GraphQL implementation ● no sugar in React components ● query batching ● state cache ● community ● combine | join graphs (Apollo server) 22
  • 23. 23 export const Article = ({ data }) => { if (data.loading) return <Spinner />; if (!data.article.id) return <NotFound />; return ( <BasePage> <ArticleViewMeta id={data.article.id} /> <BaseViewComponent {...data.article} /> <Footer /> </BasePage> ); };
  • 24. 24 const articleViewQuery = gql` query getArticleViewData($id: String) { article(id: $id){ id title text } }`; export const ArticleWithData = graphql(articleViewQuery, { options: ({ id }) => { return { variables: { id } }; }, })(Article);
  • 25. Other javascript implementations ● Relay modern / Relay classic ● Apollo client / Apollo server / Apollo engine ● Lokka ● Prisma ● GraphQL.js ● express-graphql 25
  • 27. Node.js implementation ● Only for the first request (SSR) ● Good for React prerender ● Routing ● Two-step implementation 27
  • 28. 28
  • 29. 29
  • 30. 30
  • 32. 32
  • 33. 33
  • 36. Some numbers of the new scheme ● node.js workers 8 ● React render 5 ms ● prom.ua workers over 750 ● number of requests split to node/python 36
  • 41. Sometimes it’s hard to analyze js errors from graph and Apollo 41
  • 42. 42 Hard to split routing in monolithic app
  • 43. 43 Some resources and logic need duplication
  • 44. Plans ● GraphQL everywhere ● Backend as API ● Progressive web apps ● More frontends 44