Slide 1

Slide 1 text

3 Practices about 
 Service-to-Service GraphQL Ruby Client GraphQL Tokyo #21 - Guest talk and lightning talks @qsona

Slide 2

Slide 2 text

Introduction

Slide 3

Slide 3 text

whoami • @qsona • Working at SHE, Inc. • Ex- StudySapuri • GraphQL / Ruby on Rails / Node.js / Microservices

Slide 4

Slide 4 text

4 years ago... • Urigo (The Guild) came to Japan, and GraphQL Tokyo was held • I talked about Service-to-service GraphQL

Slide 5

Slide 5 text

My argument: • GraphQL is very useful as a service-to-service protocol • not only as frontend-to-server protocol • REST, gRPC are more popular but GraphQL is better for me • Several months later - • We decided to build a new product from scratch

Slide 6

Slide 6 text

GraphQL-based Architecture

Slide 7

Slide 7 text

Today I'll talk about...

Slide 8

Slide 8 text

Practice 1 - GraphQL Client on Backend Ruby

Slide 9

Slide 9 text

On web frontend app • It's almost mandatory to use some kind of GraphQL client library • apollo-client, urql, ... • graphql-code-generator

Slide 10

Slide 10 text

On ruby app • Are there any ruby GraphQL client libraries? • github/graphql-client • Latest update: 1.5 years ago (Not actively maintained) • But... are we really need GraphQL client libraries?

Slide 11

Slide 11 text

In web frontend, GraphQL client library provides: • caching fetched data • generating static code/types (e.g. TypeScript code / d.ts) • generating codes which work with React, etc • like `useQuery`, `useFragment`, ...

Slide 12

Slide 12 text

Does the ruby app need those? • caching fetched data => unnecessary • Network cost is low • It's far more dif fi cult to cache in backend 
 because multiple user's data is mixed • generating static code/types => unnecessary • Ruby is dynamically-typed and duck-typing language • generating codes which work with React, etc => unnecessary

Slide 13

Slide 13 text

Tips • You might not need GraphQL client library • Just make a thin wrapper of HTTP client (like Net::HTTP, Faraday, etc)

Slide 14

Slide 14 text

Practice 2 - GraphQL Requests in complex command logics

Slide 15

Slide 15 text

Compare to frontend apps... • Backend app often has more complicated domain logics • Especially, Command (CUD) operations • Sometimes the operations are nested, or executed concurrently

Slide 16

Slide 16 text

Example class FooOperation def self.execute!(baz_id) ActiveRecord::Base.transaction do Foo.create! BarOperation.execute!(baz_id) end end end class BarOperation def self.execute!(baz_id) Bar.create! # more complicated logics... end end

Slide 17

Slide 17 text

FooOperation and BarOperation might need to fetch data from other services class FooOperation QUERY = "query(bazId: $ID!) { baz(id: $bazId) { a } }" def self.execute!(baz_id) data = client.execute(query: QUERY, variables: { bazId: baz_id }) ActiveRecord::Base.transaction do Foo.create!(a: data.baz.a) BarOperation.execute!(baz_id) end end end class BarOperation QUERY = "query(id: $ID!) { baz(id: $id) { b { c, d } } }" def self.execute!(baz_id) data = client.execute(query: QUERY, variables: { bazId: baz_id }) Bar.create!(c: data.baz.b.c, d: data.baz.b.d) # more complicated logics... end end

Slide 18

Slide 18 text

Problems? • Two GraphQL Requests are sent • It's mottainai! • The second request is sent in the DB transaction block • It can make the transaction longer

Slide 19

Slide 19 text

The two requests should be merged! class FooOperation QUERY = "query(bazId: $ID!) { baz(id: $bazId) { a, b { c, d } } }" def self.execute!(baz_id) data = client.execute(query: QUERY, variables: { bazId: baz_id }) ActiveRecord::Base.transaction do Foo.create!(a: data.baz.a) BarOperation.execute!(data.baz.b) end end end class BarOperation def self.execute!(b) Bar.create!(c: b.c, d: b.d) # more complicated logics... end end

Slide 20

Slide 20 text

💯 • It becomes simpler! • Problems?

Slide 21

Slide 21 text

Problems class FooOperation QUERY = "query(bazId: $ID!) { baz(id: $bazId) { a, b { c, d } } }" def self.execute!(baz_id) data = client.execute(query: QUERY, variables: { bazId: baz_id }) ActiveRecord::Base.transaction do Foo.create!(a: data.baz.a) BarOperation.execute!(data.baz.b) end end end class BarOperation def self.execute!(b) Bar.create!(c: b.c, d: b.d) # more complicated logics... end end `FooOperation` class needs to know what fi elds `BarOperation` uses It violates “Single- responsibility principle”

Slide 22

Slide 22 text

Solution: Fragment colocation

Slide 23

Slide 23 text

Solution: Fragment colocation class FooOperation QUERY = <<~EOS query(bazId: $ID!) { baz(id: $bazId) { a ...BarOperationFragment } } #{BarOperation::Fragment} EOS def self.execute!(baz_id) data = client.execute(query: QUERY, variables: { bazId: baz_id }) ActiveRecord::Base.transaction do Foo.create!(a: data.baz.a) BarOperation.execute!(data.baz) end end end class BarOperation FRAGMENT = <<~EOS fragment BarOperationFragment on Baz { b { c, d } } EOS def self.execute!(baz) Bar.create!(c: baz.b.c, d: baz.b.d) # more complicated logics... end end

Slide 24

Slide 24 text

Tips • Fragment Colocation is useful technique 
 not only for frontend but also for backend! • Sometimes you should colocate GraphQL fragment with 
 the operation (command, service, interaction...) class

Slide 25

Slide 25 text

Practice 3 - Testing

Slide 26

Slide 26 text

Testing • To test the app, we need to stub/mock the GraphQL Requests • In web frontend apps • mswjs/msw • In Ruby apps • bblimke/webmock

Slide 27

Slide 27 text

webmock stub_request(:post, "another-backend.com/graphql"). with(body: { query: FooOperation::QUERY, variables: { bazId: "1" } }). to_return(body: { data: { a: 1, b: { c: 2, d: 3 } } }.to_json)

Slide 28

Slide 28 text

This is sooooo unmaintainable! • It’s hard to manage this mock data • no static types • We need to stub for same request in several fi les • e.g. api test (request spec) and unit test • => Please use qsona/webmock-graphql • Sorry time is up 😉

Slide 29

Slide 29 text

Conclusion

Slide 30

Slide 30 text

Conclusion • GraphQL client library is not necessary on Backend Ruby • Colocate Fragments with operation classes/functions • Use (and contribute) qsona/webmock-graphql 😉