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