How are 2 very similar Graphql queries cached in Apollo client? - caching

I'm reading the Apollo client documentation but I am still confused as per how data is cached.
Let's say I have 2 queries that I use at a different hierarchy level in an app. Query A is used at the very top, while query B feeds a component several levels below.
If Apollo cache is configured cache: new InMemoryCache(), will query B use Apollo cached data from Query A, or not?
Same question if I were to use Query A for the top component and the bottom one.
Query A
query MyProjectA {
Project {
id
title
budget
}
}
Query B
query MyProjectB {
Project {
id
title
}
}

Related

Spring JPA and GraphQL

Given the following graph
type Movie {
name: String!
actors: [Actor!]!
}
type Actor {
name: String!
awards: [Award!]!
}
type Award {
name: String!
date: String!
}
type Query {
movies(): [Movie!]!
}
I'd like to be able to run the following three types of queries as efficiently as possible:
Query 1:
query {
movies {
actors {
rewards {
name
}
}
}
}
Query 2:
query {
movies {
name
}
}
Query 3:
query {
movies {
name
actors {
rewards {
date
}
}
}
}
Please note, these are not the only queries I will be running, but I'd like my code to be able to pick the optimal path "automatically".
The rest of my business logic is using JPA. The data comes from three respective tables, that can have up to 40 columns each.
I am not looking for code examples, but rather for a high-level structure describing different elements of architecture with respective responsibilities.
Without further context and details of your DB schema, what I could do is to just to give you the general advice that you need to aware of.
Most probably you would encounter N+1 loading performance issue when executing a query that contains several levels of related objects and these objects are stored in different DB tables.
Generally there are 2 ways to solve it :
Use Dataloader . Its idea is to defer the actual loading time of each object to a moment that multiple objects can be batched loaded together by a single SQL. It also provides the caching feature to further improve the loading performance for the same query request.
Use "look ahead pattern" (Refer this for an example). Its ideas is that when you resolve the parent object , you can look ahead to analyse the GraphQL query that you need to execute require to include others related children or not. If yes , you can then use the JOIN SQL to query the parent object together with their children such that when you resolve its children later , they are already fetched and you do not need to fetch them again.
Also, if the objects in your domain can contain infinity number in theory , you should consider to implement pagination behaviour for the query in order to restrict the maximum number of the objects that it can return.

Pass GraphQL Return into another GQL Call in just one Request

I want to load Jira Tickets from my Api by using one or more JiraQueryLanguage Queries.
Those Tickets are cached in my backend.
I have a database table with all JQL-Queries stored.
Now I want to get all JQL-Queries from my api and pass them into another query to get the Jira Tickets which are cached and which are included by the JQL Query Search.
And that all in just one request.
So my request might look something like this:
query GetQueries($id: Int!) {
getAllJQLQueries {
jqlQuery
}
foreach jqlQuery {
getTickets(query: jqlQuery) {
key
description
}
}
}
Currently I "solved" the problem by just calling the api multiple times but in terms of performance I would like to do it in once.
Thanks in advance!

composing queries in Apollo Client

i'm learning to use apollo client and I saw that it used to contain "compose" method which combines multiple queries, so we can call two queries in a single request without explicitly write a whole new query.
lets say I have 2 queries
const getUsersQuery = gql`query getUsers ($name:String!){
users{
name
}
}`
const getPokemonsQuery = gql`query getPokemon($id:ID!){
pokemon (id:$id) {
name
}
}`
So a compose method will return an object which I can use to fetch these two queries in single request and include the variables for the two queries.
I didn't find a way to do it in the latest version.

Should a query in Apollo Client look for the results cached by different queries before making a network request?

I'm trying to figure out how queries in Apollo Client are supposed to interact with the cache.
Specifically, I want to know if we run a query that fetches all todos:
todos {
title
completed
}
And then later we run a query that fetches a single todo that was already fetched by the todos query and requests the exact same fields:
todo(id: $id) {
title
completed
}
Should the second query a) fetch the data from the cache, or b) make a network request?
My assumption was that it would be case A. This is based on this quote from an official Apollo blog post:
https://www.apollographql.com/blog/demystifying-cache-normalization/
For example, if we were to:
Perform a GetAllTodos query, normalizing and caching all todos from a backend
Call GetTodoById on a todo that we had already retrieved with GetAllTodos
...then Apollo Client could just reach into the cache and get the object directly without making another request.
However, in my app I kept getting case B, it was always making an additional network request even though I had already requested all the data in a different query.
I assumed that I was doing something wrong, so I checked out this Apollo Full-stack Tutorial repo (https://github.com/apollographql/fullstack-tutorial) and updated the LaunchDetails query to only request the same data that was already requested in the GetLaunchList query. This replicated the same scenario I detailed above with the todos.
The queries now look like this:
export const GET_LAUNCHES = gql`
query GetLaunchList($after: String) {
launches(after: $after) {
cursor
hasMore
launches {
...LaunchTile
}
}
}
${LAUNCH_TILE_DATA}
`;
export const GET_LAUNCH_DETAILS = gql`
query LaunchDetails($launchId: ID!) {
launch(id: $launchId) {
...LaunchTile
}
}
${LAUNCH_TILE_DATA}
`;
I ran the application, and found that a new network request was made for the LaunchDetails query, even though all the required data was already in the cache after the GetLaunchList query was run.
I haven't been able to find any answer to this in the documentation, and the results I'm seeing from the example tutorial app seem to be at odds with the quote from the blog piece above.
Is it the case that a query will only look to the cache if the query has already been run before? Can it not fetch cached data if that data was cached by a different query? Am I missing something?
Please see this better (in my opinion) answer here:
https://stackoverflow.com/a/66053242/6423036
Copying directly from that answer, credit to the author:
This functionality exists, but it's hard to find if you don't know what you're looking for. In Apollo Client v2 you're looking for cache redirect functionality, in Apollo Client v3 this is replaced by type policies / field read policies (v3 docs).
Apollo doesn't 'know' your GraphQL schema and that makes it easy to set up and work with in day-to-day usage. However, this implies that given some query (e.g. getBooks) it doesn't know what the result type is going to be upfront. It does know it afterwards, as long as the __typename's are enabled. This is the default behaviour and is needed for normalized caching.
Let's assume you have a getBooks query that fetches a list of Books. If you inspect the cache after this request is finished using Apollo devtools, you should find the books in the cache using the Book:123 key in which Book is the typename and 123 is the id. If it exists (and is queried!) the id field is used as identifier for the cache. If your id field has another name, you can use the typePolicies of the cache to inform Apollo InMemoryCache about this field.
If you've set this up and you run a getBook query afterwards, using some id as input, you will not get any cached data. The reason is as described before: Apollo doesn't know upfront which type this query is going to return.
So in Apollo v2 you would use a cacheRedirect to 'redirect' Apollo to the right cache:
cacheRedirects: {
Query: {
getBook(_, args, { getCacheKey }) {
return getCacheKey({
__typename: 'Book',
id: args.id,
});
}
},
},
(args.id should be replaced by another identifier if you have specified another key in the typePolicy)
When using Apollo v3, you need a typepolicy / field read policy:
typePolicies: {
Query: {
fields: {
getBook(_, { args, toReference }) {
return toReference({
__typename: 'Book',
id: args.id,
});
}
}
}
}
the query will make a network query.
todo(id: $id) {
title
completed
}
Apollo cache isn't very smart. It is just storage. You need to read/write for more complicated operations manually.
The reason for this is Apollo doesn't know about your schema and data structure. It doesn't know that todo(id: $id) will do DB search by, so it can't optimize to look in the cache.
If you don't want a second fetch, you have to implement your data fetch structure with fragment:
try {
return client.readFragment({
id: 'Todo:5', // The value of the to-do item's unique identifier
fragment: gql`
fragment TodoFragment on Todo {
id
title
completed
}
`,
});
} catch(_e) { // if no fragment is found there will be an error
client.query(QUERY, variables: { id: 5})
}
The way Apollo cache is that if you do two queries:
load todos
todos {
id
title
completed
}
load single todo
todo(id: $id) {
id
title
completed
}
If you list a list of todos and load the second one - it will update the todo data.

How to Query fragments in a subtree of a top level query in a GraphQL server

Let's say a graphql server exists with the following schema:
query
-currentUser
--data1
--data2
...
Assume on your client, on page 1 of your app, you want data1 and page 2, you want data2. If I update data2 and want to refetch the query, do I need to query from the top level, resolving currentUser in order to get the updated data2? Is the only way around this by creating a root-level query for data2? E.g.:
query
-currentUser
--data1
--data2
-data2 (query on root of tree to avoid repeated currentUser calls)
Edit: I'm interested in how to do this using Apollo-client for React
The Relay top-level node query gives a standard way to do this. If most objects implement its Node interface, then you can use the top-level node(id: ID!) query to retrieve a specific object.
query GetFirstDatum {
currentUser {
id
data1
}
}
query GetSecondDatum($id: ID!) {
node(id: $id) {
... on User {
data2
}
}
}
This requires providing the Node interface in your IDL, having your objects implement it, and providing an implementation of the top-level node query that can return an object given its ID. Most server libraries have some level of support for this.

Resources