Slide 1

Slide 1 text

Rescuing legacy codebases with GraphQL @nettofarah

Slide 2

Slide 2 text

Netto Farah @nettofarah Eng. Manager at

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Context • Millions of users • Billions of API calls every day • Website, iOS app, Android app

Slide 6

Slide 6 text

Tech Stack (at the time) • Seasoned Rails 3 monolith app • APIs v1, v2, v3, dev_api… • Challenging to deploy/iterate/run tests • sole web dev

Slide 7

Slide 7 text

Challenge: Build an entirely new product

Slide 8

Slide 8 text

With a 9 months deadline

Slide 9

Slide 9 text

We knew we needed to make some changes

Slide 10

Slide 10 text

Majestic Monoliths vs Micro-services

Slide 11

Slide 11 text

Not a binary decision

Slide 12

Slide 12 text

A hybrid approach: Rich API + specific clients

Slide 13

Slide 13 text

How can we make our frontend and backend apps communicate?

Slide 14

Slide 14 text

Through the database?

Slide 15

Slide 15 text

Why are database-driven integrations tempting?

Slide 16

Slide 16 text

Why are database-driven integrations challenging?

Slide 17

Slide 17 text

What about APIs?

Slide 18

Slide 18 text

Challenges with Traditional APIs

Slide 19

Slide 19 text

Multiple use cases

Slide 20

Slide 20 text

Different access pattern

Slide 21

Slide 21 text

Ambiguity

Slide 22

Slide 22 text

Solved with documentation or conventions

Slide 23

Slide 23 text

[',(,)..*] => ✅ ,

Slide 24

Slide 24 text

, [-] => ask me later…

Slide 25

Slide 25 text

How can we solve a few of these challenges with APIs ?

Slide 26

Slide 26 text

Types

Slide 27

Slide 27 text

Ability to load just what we need

Slide 28

Slide 28 text

Always get predictable results

Slide 29

Slide 29 text

You know where I’m going with this, right?

Slide 30

Slide 30 text

TYPES + PREDICTABLE RESULTS + COMPOSABLE QUERIES

Slide 31

Slide 31 text

= GraphQL ❤

Slide 32

Slide 32 text

We built a GraphQL API on top of our monolith

Slide 33

Slide 33 text

GraphQL API as an integration layer for multiple (not so micro) services

Slide 34

Slide 34 text

GraphQL API ——— MonoRail ☁ A P I 
 G
 A
 T E
 W
 A Y Service A Service B Service C

Slide 35

Slide 35 text

GraphQL API ——— MonoRail ⛈ A P I 
 G
 A
 T E
 W
 A Y Service A Service B Service C

Slide 36

Slide 36 text

GraphQL (and Rails) in production

Slide 37

Slide 37 text

Challenge #1

Slide 38

Slide 38 text

N+1 queries

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

query { recipes { title ingredients { name vendor { name } } } }

Slide 41

Slide 41 text

SELECT "recipes".* FROM “recipes" SELECT "ingredients".* FROM "ingredients" WHERE "ingredients"."recipe_id" = 1 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 1 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 2 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 3 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 4 SELECT "ingredients".* FROM "ingredients" WHERE "ingredients"."recipe_id" = 2 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 5 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 6 SELECT "ingredients".* FROM "ingredients" WHERE "ingredients"."recipe_id" = 3 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 7 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 8 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 9 SELECT "ingredients".* FROM "ingredients" WHERE "ingredients"."recipe_id" = 4 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 10 SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" = 11

Slide 42

Slide 42 text

SELECT "ingredients".* FROM "ingredients" WHERE "ingredients"."recipe_id" = 1 SELECT * FROM "vendors" WHERE "id" = 1 SELECT * FROM "vendors" WHERE "id" = 2 SELECT * FROM "vendors" WHERE "id" = 3 SELECT * FROM "vendors" WHERE "id" = 4

Slide 43

Slide 43 text

How do people usually solve this problem?

Slide 44

Slide 44 text

DataLoader but that’s a javascript only tool

Slide 45

Slide 45 text

GraphQL-Batch resolve -> (obj, args, context) do Loader.for(Product).load(args["id"]).then do |p| Loader.for(Image).load(p.image_id) end end

Slide 46

Slide 46 text

Let’s take a second look at our data models

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

Recipe.all.includes({ ingredients: 'vendor' })

Slide 49

Slide 49 text

query { recipes { title ingredients { name vendor } } } Recipe.all.includes({ ingredients: 'vendor' })

Slide 50

Slide 50 text

SELECT "recipes".* FROM "recipes"
 SELECT "ingredients".* FROM “ingredients” WHERE “ingredients"."recipe_id" IN (1, 2, 3, 4) 
 SELECT "vendors".* FROM “vendors" WHERE “vendors"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

Slide 51

Slide 51 text

github.com/nettofarah/graphql-query-resolver

Slide 52

Slide 52 text

~60% reduction in database IOPS =

Slide 53

Slide 53 text

Selectively choosing our Database environment

Slide 54

Slide 54 text

if includes_mutation?(query) DatabaseSelection.use_main_database do GraphQL.execute_query(query) end else DatabaseSelection.use_readonly_replica do GraphQL.execute_query(query) end end

Slide 55

Slide 55 text

☠ Eliminated contention locks

Slide 56

Slide 56 text

Lessons

Slide 57

Slide 57 text

#1 Figure out batching as early as you can

Slide 58

Slide 58 text

#2 Leverage GraphQL types

Slide 59

Slide 59 text

Challenge #2

Slide 60

Slide 60 text

Monitoring and Errors

Slide 61

Slide 61 text

This is not really useful

Slide 62

Slide 62 text

What’s up with my
 errors?

Slide 63

Slide 63 text

#2 Leverage GraphQL types (again) Lesson

Slide 64

Slide 64 text

# At the field level new_resolver = -> (obj, args, ctx) { name = [“GraphQL/field/#{type.name}.#{field.name}"] NewRelic.trace_execution_scoped(name) do old_resolver.call(obj, args, ctx) end } # At the query level NewRelic::Agent.set_transaction_name(query_name)

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

http://bit.ly/gql-rb-nr

Slide 69

Slide 69 text

#3 Proper monitoring is as important as good performance

Slide 70

Slide 70 text

#4 GraphQL is awesome

Slide 71

Slide 71 text

@nettofarah [email protected]