$30 off During Our Annual Pro Sale. View Details »

GraphQL in production

GraphQL in production

2018/03/03 YAPC::Okinawa 2018 ONNASON

Ryo.Nitami

March 03, 2018
Tweet

More Decks by Ryo.Nitami

Other Decks in Technology

Transcript

  1. GraphQL Λ
    ϓϩμΫγϣϯಋೖͨ݁͠Ռ
    YAPC::Okinawa 2018 ONNASON 1

    View Slide

  2. ࣗݾ঺հ
    — ਔଟݟ ྒྷʢʹͨΈ Γΐ͏ʣ
    @bird_tummy
    — Android, iOS application
    developer @ CyberAgent, Inc.
    — ❤ SHISHAMO ! ! !
    YAPC::Okinawa 2018 ONNASON 2

    View Slide

  3. ࣗݾ঺հ
    — Perl શ͘ॻ͍ͨ͜ͱ͋Γ·ͤΜʂʂʂʂʂ
    — ͔ͤͬ͘ YAPC ʹདྷͨͷͰษڧͯ͠ؼΖ͏ͱࢥ͍·
    ͢ʂʂʂʂ
    YAPC::Okinawa 2018 ONNASON 3

    View Slide

  4. ࠓ೔࿩͢͜ͱ
    — ࡢ೥͔ΒΑ͘ݟ͔͚ΔΑ͏ʹͳͬͨ GraphQL
    — GitHub API v4 Ͱ࠾༻͞Εͨͷ͕͖͔͚ͬͷ 1 ͭ
    — ͳ͔ͳ͔࣮૷पΓͷ஌ݟ͕޿·͍ͬͯͳ͍Α͏ʹࢥ͏
    ʢࢼͨ͠ܥهࣄ͸͋Δ͚Ͳɻɻʣ
    YAPC::Okinawa 2018 ONNASON 4

    View Slide

  5. ࠓ೔࿩͢͜ͱ
    — ಋೖฤɺ࣮૷ฤɺӡ༻ฤͷ 3 ͭͷϑΣʔζʹ෼͚ͯͲͷΑ
    ͏ʹ࣮૷͍ͯ͠Δ͔Λ͓࿩͠·͢
    — ΫϥΠΞϯτ੒෼ଟΊͰ͢
    — ࠓޙ࣮ࡍʹಋೖ͠Α͏ͱ͍ͯ͠Δํɺݕ౼͍ͯ͠Δํͷࢀ
    ߟʹͳΕ͹޾͍Ͱ͢
    YAPC::Okinawa 2018 ONNASON 5

    View Slide

  6. ࠓ೔࿩͞ͳ͍͜ͱ
    — GraphQL ͱ REST ౳ͱͷൺֱ
    — GraphQL ʹ͍ͭͯͷࡉ͔͍ղઆ
    YAPC::Okinawa 2018 ONNASON 6

    View Slide

  7. ΞδΣϯμ
    — ͦ΋ͦ΋ GraphQL ͱ͸ʁ
    — ಋೖฤ
    — GraphQL ͷ։ൃ؀ڥ
    — ࣮૷ฤ
    — GraphQL ͷαʔό / ΫϥΠΞϯτͦΕͧΕͷ࣮૷ํ๏
    — ӡ༻ฤ
    — Ωϟογϡ౳ͷύϑΥʔϚϯε໘ʹ͍ͭͯ
    — ςετͷॻ͖ํ
    — ·ͱΊ
    YAPC::Okinawa 2018 ONNASON 7

    View Slide

  8. GraphQL ͱ͸ʁ
    YAPC::Okinawa 2018 ONNASON 8

    View Slide

  9. GraphQL ͱ͸ʁ
    — http://graphql.org
    — Facebook ੡
    — API ༻ͷΫΤϦݴޠͰ͋Γɺطଘ
    ͷσʔλͰΫΤϦΛ࣮ߦ͢ΔͨΊ
    ͷϥϯλΠϜ
    YAPC::Okinawa 2018 ONNASON 9

    View Slide

  10. YAPC::Okinawa 2018 ONNASON 10

    View Slide

  11. GraphQL ͱ͸ʁ
    — Mutation ͱ Query ͕͋Δ
    — Mutation: REST API Ͱ͍͏ POST, PUT, DELETE
    — Query: REST API Ͱ͍͏ GET
    — Fragment Ͱಉ͡ఆٛΛ࢖͍·ΘͤΔ
    YAPC::Okinawa 2018 ONNASON 11

    View Slide

  12. YAPC::Okinawa 2018 ONNASON 12

    View Slide

  13. YAPC::Okinawa 2018 ONNASON 13

    View Slide

  14. YAPC::Okinawa 2018 ONNASON 14

    View Slide

  15. ಋೖฤ
    YAPC::Okinawa 2018 ONNASON 15

    View Slide

  16. ಋೖฤ
    — ·ͣखݩͰࢼͤΔ؀ڥΛ༻ҙ͢Δ
    — GraphiQL (GraphiQL Feen!)
    — υΩϡϝϯτੜ੒
    — graphdoc
    YAPC::Okinawa 2018 ONNASON 16

    View Slide

  17. GraphiQL (GraphiQL Feen!)
    — https://github.com/graphql/graphiql
    — ϒϥ΢βͰಈ͘
    — https://github.com/skevy/graphiql-app
    — Electron ੡
    — https://github.com/bpatters/graphiql-feen
    — Chrome Extension
    YAPC::Okinawa 2018 ONNASON 17

    View Slide

  18. YAPC::Okinawa 2018 ONNASON 18

    View Slide

  19. YAPC::Okinawa 2018 ONNASON 19

    View Slide

  20. ಋೖฤ
    — υΩϡϝϯτͱ࢓༷Λݟͳ͕Βɺ֤ Mutation ΍ Query Ͱ
    ΄͍͠΋ͷ͚ͩఆٛ͢Δ
    — .graphql, .gql
    — ఆٛ͢Δͱ͖ʹิ׬͕͖͍ͨঢ়ଶͰ΍Γ͍ͨ
    — ֤ΤσΟλͷϓϥάΠϯ͕࡞ΒΕ͍ͯΔ
    YAPC::Okinawa 2018 ONNASON 20

    View Slide

  21. ಋೖฤ
    — vscode-graphql
    — js-graphql-intellij-plugin
    YAPC::Okinawa 2018 ONNASON 21

    View Slide

  22. ಋೖฤ
    — intellij ͷํΛ͕ͬͭΓ࢖ͬͯΔͷͰͦͪΒΛྫʹݟͯΈΔ
    YAPC::Okinawa 2018 ONNASON 22

    View Slide

  23. ಋೖฤ
    — js-graphql-intellij-plugin
    — https://github.com/jimkyndemeyer/js-graphql-
    intellij-plugin
    — graphql.config.json ʹ API ͷΤϯυϙΠϯτ΍ඞཁͳ
    ϔομʔΛઃఆͯ͠࢖༻
    YAPC::Okinawa 2018 ONNASON 23

    View Slide

  24. {
    "schema": {
    "file": "graphql.schema.json",
    "request": {
    "url" : "http://--graphql-server--/path-to-schema-json-or-introspection-endpoint",
    "method" : "POST",
    "postIntrospectionQuery" : true,
    "README_options" : "See the 'Options' section at https://github.com/then/then-request",
    "options" : {
    "headers": {
    "user-agent" : "JS GraphQL"
    }
    }
    }
    },
    "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE",
    "endpoints" : [
    {
    "name": "Default (http://localhost:8080/graphql)",
    "url": "http://localhost:8080/graphql",
    "options" : {
    "headers": {
    "user-agent" : "JS GraphQL"
    }
    }
    }
    ]
    }
    YAPC::Okinawa 2018 ONNASON 24

    View Slide

  25. YAPC::Okinawa 2018 ONNASON 25

    View Slide

  26. ಋೖฤ·ͱΊ
    — GraphiQL Λ࢖͑͹खݩͰࢼͤΔ
    — graphql ϑΝΠϧʹฦΓ஋ͷఆٛΛॻ͕͘ɺॻ͘ࡍͷαϙʔτΛͯ͘͠ΕΔΤσΟλ
    ͷϓϥάΠϯ͕ఏڙ͞Ε͍ͯΔ
    — vscode
    — Intellij
    — etc.
    — ࠓͷͱ͜Ζ࢖͍ͬͯΔϓϥάΠϯͰࠔ͍ͬͯΔ͜ͱ͸ͳ͍͕ɺ͜Ε͕͋ͬͨΒͳʔͱ
    ͍͏ͱ͜Ζ͕গ͋͠Δ
    — graphql.config.json ͷ؅ཧ͕೉͍͠
    — ߋ৽͕ࢭ·͍͍ͬͯͯͭ࢖͑ͳ͘ͳΔ͔Θ͔Βͳ͍
    YAPC::Okinawa 2018 ONNASON 26

    View Slide

  27. ࣮૷ฤ
    YAPC::Okinawa 2018 ONNASON 27

    View Slide

  28. αʔόαΠυ
    — apollo-server
    — https://github.com/apollographql/apollo-server
    — Լهͷத͔Β޷͖ͳ΋ͷΛબΜͰ࢖͏͜ͱ͕Ͱ͖Δ
    — Express
    — koa
    — hapi
    — restify
    — lambda
    — micro
    — azure-functions
    — adonis
    YAPC::Okinawa 2018 ONNASON 28

    View Slide

  29. apollo-server-koa
    import koa from 'koa'; // koa@2
    import koaRouter from 'koa-router'; // koa-router@next
    import koaBody from 'koa-bodyparser'; // koa-bodyparser@next
    import { graphqlKoa, graphiqlKoa } from 'apollo-server-koa';
    const app = new koa();
    const router = new koaRouter();
    const PORT = 3000;
    // koaBody is needed just for POST.
    router.post('/graphql', koaBody(), graphqlKoa({ schema: myGraphQLSchema }));
    router.get('/graphql', graphqlKoa({ schema: myGraphQLSchema }));
    router.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' }));
    app.use(router.routes());
    app.use(router.allowedMethods());
    app.listen(PORT);
    YAPC::Okinawa 2018 ONNASON 29

    View Slide

  30. ϑϩϯτ
    — @nuxtjs/apollo
    — https://github.com/nuxt-community/apollo-module
    — vue-apollo ͱ͍͏ .graphql ϑΝΠϧͷ Loader Λ
    nuxtjs Ͱ࢖͑ΔΑ͏ʹͨ͠΋ͷ
    YAPC::Okinawa 2018 ONNASON 30

    View Slide

  31. @nuxtjs/apollo
    import { ApolloLink } from 'apollo-link'
    import { HttpLink } from 'apollo-link-http'
    import { InMemoryCache } from 'apollo-cache-inmemory'
    export default (ctx) => {
    const httpLink = new HttpLink({ uri: 'http://localhost:8000/graphql' })
    // middleware
    const middlewareLink = new ApolloLink((operation, forward) => {
    const token = process.server ? ctx.req.session : window.__NUXT__.state.session
    operation.setContext({
    headers: { authorization: `Bearer ${token}` }
    })
    return forward(operation)
    })
    const link = middlewareLink.concat(httpLink)
    return {
    link,
    cache: new InMemoryCache()
    }
    }
    YAPC::Okinawa 2018 ONNASON 31

    View Slide

  32. iOS
    — apollo-ios
    — https://github.com/apollographql/apollo-ios
    — apollo-codegen
    — αʔόଆͰఆٛ͞Ε͍ͯΔ schema Λݩʹ Swift ϑΝ
    ΠϧΛੜ੒͢Δ CLI
    YAPC::Okinawa 2018 ONNASON 32

    View Slide

  33. apollo-ios
    let queryString = "GraphQL"
    let configuration: URLSessionConfiguration = .default
    configuration.httpAdditionalHeaders = ["Authorization": "Bearer \(token)"]
    configuration.requestCachePolicy = .reloadIgnoringLocalCacheData // To avoid 412
    let url = URL(string: "https://api.github.com/graphql")!
    let apollo = ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration))
    apollo.fetch(query: SearchRepositoriesQuery(query: queryString, count: 2), completionHandler: { (result, error) in
    if let error = error { print("Error: \(error)"); return }
    result?.data?.search.edges?.forEach { edge in
    guard let repository = edge?.node?.asRepository else { return }
    print("Name: \(repository.name)")
    print("Path: \(repository.url)")
    print("Owner: \(repository.owner.path)")
    print("Stars: \(repository.stargazers.totalCount)")
    }
    })
    YAPC::Okinawa 2018 ONNASON 33

    View Slide

  34. Android
    — apollo-android
    — https://github.com/apollographql/apollo-android
    — apollo-codegen
    — αʔόଆͰఆٛ͞Ε͍ͯΔ schema Λݩʹ Java ϑΝΠ
    ϧΛੜ੒͢Δ CLI
    YAPC::Okinawa 2018 ONNASON 34

    View Slide

  35. apollo-android
    private void fetchRepositoryDetails() {
    ApolloCall entryDetailQuery = application.apolloClient()
    .query(new EntryDetailQuery(repoFullName))
    .responseFetcher(ApolloResponseFetchers.CACHE_FIRST);
    //Example call using Rx2Support
    disposables.add(Rx2Apollo.from(entryDetailQuery)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeWith(new DisposableObserver>() {
    @Override public void onNext(Response dataResponse) {
    setEntryData(dataResponse.data());
    }
    @Override public void onError(Throwable e) {
    Log.e(TAG, e.getMessage(), e);
    }
    @Override public void onComplete() {
    }
    }));
    }
    YAPC::Okinawa 2018 ONNASON 35

    View Slide

  36. apollo-codegen
    — https://github.com/apollographql/apollo-codegen
    — αʔόଆͰఆٛ͞Ε͍ͯΔ schema Λݩʹ Java ϑΝΠϧ
    Λੜ੒͢Δ CLI
    — Node.js ੡
    YAPC::Okinawa 2018 ONNASON 36

    View Slide

  37. apollo-codegen
    $ apollo-codegen generate **/*.graphql --schema schema.json --output API.swift
    YAPC::Okinawa 2018 ONNASON 37

    View Slide

  38. apollo-codegen
    public struct AsRepository: GraphQLConditionalFragment {
    public static let possibleTypes = ["Repository"]
    public let __typename = "Repository"
    public let name: String
    public let owner: Owner
    public let stargazers: Stargazer
    public let url: String
    public init(reader: GraphQLResultReader) throws {
    name = try reader.value(for: Field(responseName: "name"))
    owner = try reader.value(for: Field(responseName: "owner"))
    stargazers = try reader.value(for: Field(responseName: "stargazers"))
    url = try reader.value(for: Field(responseName: "url"))
    }
    public struct Owner: GraphQLMappable {
    public let __typename: String
    public let path: String
    public init(reader: GraphQLResultReader) throws {
    __typename = try reader.value(for: Field(responseName: "__typename"))
    path = try reader.value(for: Field(responseName: "path"))
    }
    }
    public struct Stargazer: GraphQLMappable {
    public let __typename = "StargazerConnection"
    public let totalCount: Int
    public init(reader: GraphQLResultReader) throws {
    totalCount = try reader.value(for: Field(responseName: "totalCount"))
    }
    }
    }
    YAPC::Okinawa 2018 ONNASON 38

    View Slide

  39. ࣮૷ฤ·ͱΊ
    — apollo ʹײँ !
    — ͍͍ͩͨͷݴޠ͕αϙʔτ͞Ε͍ͯΔ
    — Perl ͸ͳ͔ͬͨ (´ɾТɾʆ)
    — Shopify Ͱ࢖ΘΕ͍ͯͯɺͦͷ։ൃऀ͕ϝϯςφϯε͍ͯ͠
    ΔͷͰ҆৺
    — ࡶͳ issue ΋ड͚෇͚ͯ͘ΕΔͷͰػೳͷཁ๬ͱ͔౤͛ͯ
    ΈΔͱ͍͍͔΋
    YAPC::Okinawa 2018 ONNASON 39

    View Slide

  40. YAPC::Okinawa 2018 ONNASON 40

    View Slide

  41. ӡ༻ฤ
    YAPC::Okinawa 2018 ONNASON 41

    View Slide

  42. ӡ༻ฤ
    — ΩϟογϡͬͯͲ͏ͳΔͷʁ
    — apollo-android Ͱ͸ 3 ͭͷํ๏Λαϙʔτ
    — HTTP Response Cache: ੜͷϨεϙϯεΛϑΝΠϧʹ͠
    ͯΩϟογϡ
    — Normalized Disk Cache: SQL ʹಥͬࠐΜͰΩϟογϡ
    — Normalized InMemory Cache: ϝϞϦʹͭͬ͜ΜͰΩ
    ϟογϡ
    YAPC::Okinawa 2018 ONNASON 42

    View Slide

  43. // HTTP Response Cache
    //Directory where cached responses will be stored
    File file = new File("/cache/");
    //Size in bytes of the cache
    int size = 1024*1024;
    //Create the http response cache store
    DiskLruHttpCacheStore cacheStore = new DiskLruCacheStore(file, size);
    //Build the Apollo Client
    ApolloClient apolloClient = ApolloClient.builder()
    .serverUrl("/")
    .httpCache(new ApolloHttpCache(cacheStore))
    .okHttpClient(okHttpClient)
    .build();
    apolloClient
    .query(
    FeedQuery.builder()
    .limit(10)
    .type(FeedType.HOT)
    .build()
    )
    .httpCachePolicy(HttpCachePolicy.CACHE_FIRST)
    .enqueue(new ApolloCall.Callback() {
    @Override public void onResponse(@Nonnull Response dataResponse) {
    ...
    }
    @Override public void onFailure(@Nonnull Throwable t) {
    ...
    }
    });
    YAPC::Okinawa 2018 ONNASON 43

    View Slide

  44. ӡ༻ฤ
    — CACHE_ONLY
    — Ωϟογϡ͚ͩݟʹߦ͘ɻAPI ୟ͍ͯऔಘ͠ʹ͍͔ͳ͍ɻΩϟογϡ͕ଘࡏ͠ͳ͔ͬͨ
    ͱ͖͸ΤϥʔʹͳΔ
    — NETWORK_ONLY
    — ΩϟογϡΛݟʹߦ͔ͣʹຖճ API Λୟ͍ͯऔಘ
    — CACHE_FIRST
    — ΩϟογϡΛ·ͣୈҰʹݟʹߦ͘ɻΩϟογϡ͕ͳ͔ͬͨΓͨ͠ͱ͖͸ API Λୟ͖ʹߦ
    ͘
    — NETWORK_FIRST
    — API Λ·ͣୈҰʹୟ͖ʹߦ͘ɻAPI ϨεϙϯεͰΤϥʔ͕ฦ͖ͬͯͯɺΩϟογϡ͕·ͩ
    ࢒͍ͬͯΕ͹ͦΕΛ࢖͏
    YAPC::Okinawa 2018 ONNASON 44

    View Slide

  45. ӡ༻ฤ
    — Query ͸͍ͣΕ͔ͷํ๏Λ࢖͑͹ΩϟογϡՄೳ
    — Mutation ͸ෆՄ
    YAPC::Okinawa 2018 ONNASON 45

    View Slide

  46. ӡ༻ฤ
    — ςετͷॻ͖ํ
    — UI ςετ͸جຊ΍Βͳ͍
    — ωοτϫʔΫΤϥʔͷςετ
    — ಛఆͷ Type ʹͪΌΜͱύʔεͰ͖Δ͔Ͳ͏͔
    YAPC::Okinawa 2018 ONNASON 46

    View Slide

  47. @Test public void onNetworkError() throws Exception {
    final CountDownLatch countDownLatch = new CountDownLatch(1);
    final AtomicBoolean invoked = new AtomicBoolean();
    final Handler callbackHandler = mockCallbackHandler(invoked);
    final AtomicReference exceptionRef = new AtomicReference<>();
    apolloClient.query(EMPTY_QUERY).enqueue(ApolloCallback.wrap(new ApolloCall.Callback() {
    @Override public void onResponse(@Nonnull Response response) {
    countDownLatch.countDown();
    }
    @Override public void onFailure(@Nonnull ApolloException e) {
    countDownLatch.countDown();
    }
    @Override public void onNetworkError(@Nonnull ApolloNetworkException e) {
    exceptionRef.set(e);
    countDownLatch.countDown();
    }
    }, callbackHandler));
    countDownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    assertThat(invoked.get()).isTrue();
    assertThat(exceptionRef.get()).isInstanceOf(ApolloNetworkException.class);
    }
    YAPC::Okinawa 2018 ONNASON 47

    View Slide

  48. @Test public void onResponse() throws Exception {
    final CountDownLatch countDownLatch = new CountDownLatch(1);
    final AtomicBoolean invoked = new AtomicBoolean();
    final Handler callbackHandler = mockCallbackHandler(invoked);
    final AtomicReference responseRef = new AtomicReference<>();
    server.enqueue(new MockResponse().setResponseCode(200).setBody("{" +
    " \"errors\": [" +
    " {" +
    " \"message\": \"Cannot query field \\\"names\\\" on type \\\"Species\\\".\"," +
    " \"locations\": [" +
    " {" +
    " \"line\": 3," +
    " \"column\": 5" +
    " }" +
    " ]" +
    " }" +
    " ]" +
    "}"));
    apolloClient.query(EMPTY_QUERY).enqueue(ApolloCallback.wrap(new ApolloCall.Callback() {
    @Override public void onResponse(@Nonnull Response response) {
    responseRef.set(response);
    countDownLatch.countDown();
    }
    @Override public void onFailure(@Nonnull ApolloException e) {
    countDownLatch.countDown();
    }
    }, callbackHandler));
    countDownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    assertThat(invoked.get()).isTrue();
    assertThat(responseRef.get()).isNotNull();
    }
    YAPC::Okinawa 2018 ONNASON 48

    View Slide

  49. ·ͱΊ
    — GraphQL ͷ࣮૷ʹ͍ͭͯ͝঺հ͠·ͨ͠
    — ಋೖ͸ϥΠϒϥϦͷ README ͕੔͍ͬͯΔ͓͔͛ͰָʹՄೳ
    — ࣮૷ɾӡ༻͸ϥΠϒϥϦʹࠨӈ͞ΕΔ
    — ֤ϓϥοτϑΥʔϜͰศརͳϥΠϒϥϦ͕ἧ͍ͬͯΔͨΊɺී௨ʹ࣮
    ૷͸Ͱ͖ͦ͏
    — ·ͩ·ͩࢀߟهࣄ͕গͳ͘ɺͪΐͬͱ͸·Δͱݫ͍͠ͱ͖͕͋Δ
    — ͔͜͜Β੝Γ্͕͖ͬͯͯɺ஌ݟ͕ͨ͘͞ΜΞ΢τϓοτ͞ΕΔͷ
    Λظ଴ʂ
    YAPC::Okinawa 2018 ONNASON 49

    View Slide

  50. ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ
    ࠙਌ձ·Ͱ͍ΔͷͰԿ͔࣭໰౳͋Ε͹੠͔͚͍ͯͩ͘͞ʂ
    ʢTwitter ͔ΒͰ΋ OK Ͱ͢ʣ
    YAPC::Okinawa 2018 ONNASON 50

    View Slide