graphql-complete

module
v0.0.0-...-ecf25e1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2024 License: Apache-2.0

README

Example: GraphQL and Resource Servers

In this example, dataloader is used to reduce the risk of duplicative or N+1 queries between a GraphQL server (built from gqlgen) and a toy resource server that implements list endpoints that support multiple IDs as inputs.

Other than data storage, which is simple in-memory data populated by JSON files, I have tried to make this example as "real" as possible. There are comments scattered throughout the code to help understand what's going on.

Run the Example

After cloning this repo, open two terminal windows in this directory. In one window, run the resource server:

go run cmd/resource-server/main.go

In the other terminal, run the GraphQL server:

go run cmd/graph-server/main.go

You can add the -help flag to see the available flags for either command.

If you're using the defaults, open a browser to http://localhost:8080/ and you should be presented with the GraphiQL playground. Introspection is on so you can use the playground to explore the queries available.

Collapsing Requests

Naive implementations of GraphQL resolvers can end up making separate requests to resource servers for each, potentially nested or duplicated, resource returned by the query. To see how dataloader collapses these individual requests, try this query:

{
  book(id:"urn:isbn:978-1942788331"){
    title
    authors {
      name
    }
  }
}

The resulting book has three authors, and we're fetching their names. However, if you look at the logging output in the resource server terminal, you will see two requests:

GET /books?id=urn%3Aisbn%3A978-1942788331
GET /people?id=urn%3Aperson%3Agene-kim&id=urn%3Aperson%3Anicole-forsgren&id=urn%3Aperson%3Ajez-humble

The first request, to the "books" resource server, fetches a single result by its ID. The second request, to the "people" resource server, fetches all three authors in one request.

Collapsing Across Queries

One risk of GraphQL is that a single graph query can result in several backend lookups due to the structure of the query. In this example, constructing a query that looks up two books results in two concurrent calls to the Book resolver, which typically means two requests to the book resource server. Using dataloader, we're able to collapse concurrent requests and the nested resource requests.

{
  accel: book(id: "urn:isbn:978-1942788331") {
    title
    authors {
      id
      name
    }
  }
  staff: book(id: "urn:isbn:978-1736417911") {
    id
    title
    authors {
      name
    }
  }
}

The logs again show two requests, one to each resource server:

GET /books?id=urn%3Aisbn%3A978-1942788331&id=urn%3Aisbn%3A978-1736417911
GET /people?id=urn%3Aperson%3Anicole-forsgren&id=urn%3Aperson%3Ajez-humble&id=urn%3Aperson%3Agene-kim&id=urn%3Aperson%3Atanya-reilly&id=urn%3Aperson%3Awill-larson

dataloader turns up to 7 separate requests (2 for books and 5 for authors) into one request per resource type.

Deduplication

A consequence of how dataloader collapses requests is that individual resources can be reused across concurrent requests (even potentially across concurrent users)! In this example, we have three books with a total of two authors across them.

{
  execs: book(id: "urn:isbn:978-1098149482") {
    title
    authors {
      id
      name
    }
  }
  staff: book(id: "urn:isbn:978-1736417911") {
    id
    title
    authors {
      name
    }
  }
  staffPath: book(id:"urn:isbn:978-1098118730") {
    title
    authorIDs
    authors {
      name
    }
  }
}

In the logs we again see just two requests, and in particular each author is only requested once and used to satisfy multiple concurrent lookups:

GET /books?id=urn%3Aisbn%3A978-1098149482&id=urn%3Aisbn%3A978-1736417911&id=urn%3Aisbn%3A978-1098118730
GET /people?id=urn%3Aperson%3Atanya-reilly&id=urn%3Aperson%3Awill-larson
Partial Failures

GraphQL is able to return partial errors for a given query if there are failures with some resources but not all. Similarly, dataloader can handle the case of missing results for a given lookup batch.

{
  fake: book(id: "urn:isbn:978-notreal") {
    title
    authors {
      name
    }
  }
  phb: book(id: "urn:isbn:978-0786965601") {
    id
    title
    authorIDs
    authors {
      name
    }
  }
}

Directory Walkthrough

This example contains the source code for both the GraphQL and resource servers, but only code in shared/ is used by both.

shared/middleware/

Since both servers use HTTP, they both use the same request logging middleware, defined here.

GraphQL Server

The GraphQL server starts in cmd/graph-server/main.go and largely follows the default layout of a gqlgen project.

  • graph/ contains the generated code and resolver implementations. The most important thing to note is that the resolvers in graph/schema.resolvers.go use dataloader to load data in parallel. Serial requests cannot be collapsed.
  • schema/ contains the GraphQL schema definition used to generate the server code. If you make any changes in schema, re-run go run github.com/99designs/gqlgen generate in the root of this example.
  • fetchers/ contains the required fetching implementation that, given a set of unique IDs provided by dataloader, makes a single request to the appropriate resource server.
  • tools.go ensures that the correct version of gqlgen is used.
  • gqlgen.yml is the configuration for gqlgen.
Resource Server

The Resource server is an REST-ish HTTP server that exposes two "list" endpoints, one at /books and one at /people, that support filtering by multiple IDs.

The Resource server starts in cmd/resource-server/main.go and is primarily implemented within handlers/.

Both handlers/books and handlers/people follow a similar pattern:

  • New() loads a set of data from JSON files stored in the directory and embedded into the binary. It constructs a local key-value store and returns an http.Handler.
  • Handler.ServeHTTP looks at any id= query string parameters, pulls the requisite IDs out of the key-value store, and builds a JSON response.

Directories

Path Synopsis
cmd
graph-server command
resource-server command
shared

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL