Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for refetching part of a GraphQL query with Apollo?

I have the following react-apollo-wrapped GraphQL query:

user(id: 1) {
  name
  friends {
    id
    name
  }
}

As semantically represented, it fetches the user with ID 1, returns its name, and returns the id and name of all of its friends.

I then render this in a component structure like the following:

graphql(ParentComponent)
  -> UserInfo
  -> ListOfFriends (with the list of friends passed in)

This is all working for me. However, I wish to be able to refetch the list of friends for the current user.

I can do this.props.data.refetch() on the parent component and updates will be propagated; however, I'm not sure this is the best practice, given that my GraphQL query looks something more like this,

user(id: 1) {
  name
  foo1
  foo2
  foo3
  foo4
  foo5
  ...
  friends {
    id
    name
  }
}

Whilst the only thing I wish to refetch is the list of friends.

What is the best way to cleanly architect this? I'm thinking along the lines of binding an initially skipped GraphQL fetcher to the ListOfFriends component, which can be triggered as necessary, but would like some guidance on how this should be best done.

Thanks in advance.

like image 507
Lucas Avatar asked Jan 02 '18 20:01

Lucas


1 Answers

I don't know why you question is downvoted because I think it is a very valid question to ask. One of GraphQL's selling points is "fetch less and more at once". A client can decide very granually what it needs from the backend. Using deeply nested graphlike queries that previously required multiple endpoints can now be expressed in a single query. At the same time over-fetching can be avoided. Now you find yourself with a big query, everything loads at once and there are no n+1 query waterfalls. But now you know that a few fields in your big query are subject to change from now and then and you want to actively update the cache with new data from the server. Apollo offers the refetch field but it loads the whole query which clearly is overfetching that was sold to me as not being a concern anymore in GraphQL. Let me offer some solutions:

Premature Optimisation?

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming. - Donald Knuth

Sometimes we try to optimise too much without measuring first. Write it the easy way first and then see if it is really an issue. What exactly is slow? The network? A particular field in the query? The sheer size of the query?

After you analized what exactly is slow we can start looking into improving:

Refetch and include/skip directives

Using directives you can exclude fields from a query depending on variables. The refetch function can specify different variables than the initial query. This way you can exclude fields when you refetch the query.

Splitting up Queries

Single page apps are a great idea. The HTML is generated client side and the page does not have to make expensive trips to the server to render a new page. But soon SPAs got to big and code splitting became an issue. And now we are basically back to server side rendering and splitting the app into pages. The same might apply to GraphQL. Sometimes queries are too big and should be split. You could split up the queries for UserInfo and ListOfFriends. Inside of the cache the fields will be merged. With query batching both queries will be send in the same request and a GraphQL server that implements per request resource caching correctly (e.g. with Dataloader) will barely notice a difference.

Subscriptions

Maybe you are ready to use subscriptions already. Subscriptions send updates from the server for fields that have changed. This way you could subscribe to a user's friends and get updates in real time. The good news is that Apollo Client, Relay and many server implementations offer support for subscriptions already. The bad news is that it needs websockets that usually put different requirements on your technology stack than pure HTTP.

withApollo() -> this.client.query

This should only be your last resort! Using react-apollo's withApollo higher order component you can directly inject the ApolloClient instance. You can now execute queries using this.client.query(). { user(id: 1) { friendlist { ... } } } can be used to just fetch the friend list and update the cache which will lead to an update of your component. This might look like what you want but can haunt you in later stages of the app.

like image 89
Herku Avatar answered Sep 27 '22 19:09

Herku