Apollo Client Note

黃子洋
7 min readJan 13, 2022

Motivation

近期因為公司專案由RestFul轉成GraphQL,所以近期蠻多時間都在研究GraphQL相關的東西,畢竟有時程的壓力所以直接選用了最多人使用的Client Framework - Apollo,並在上禮拜開始研究。

如果日後有時間希望可以好好讀一遍GraphQL的文檔,再回頭來讀這些像是Apollo, Urql, Relay這些Client Framework想必會有更深的體悟,這篇文章主要紀錄的是Apollo Client caching的部分

Caching 迷思

Caching不管實作在前端或是後端都有機會讓你的App提高效能,這邊說有機會不是沒有原因的,並不是用了caching就一定會讓你的App變得比較快速,想想看以下的因素

Caching以Redis為例子,是儲存在本地的一個In-memory database,一般的使用情境會在訪問DB前去訪問Caching server來看有無之前就儲存下來的資料,若有則會省去訪問DB的時間,把該資料從Caching layer取出並回傳,但假如說你每次去訪問Caching layer時都拿不到想要的資料,這時在前端向後端提取資料的過程中就多了一個訪問Caching layer的過程,Caching layer進而變成了拖累效能的東西。

其實這段與本篇內容無太大的關係,只是想表達實作Caching layer以及挑選Caching strategy的重要性。

Apollo Client Caching Overview

在Apollo的官網上其實就有一張圖良好的解釋到Client Caching什麼時候會被訪問的流程

https://www.apollographql.com/docs/react/caching/overview/

Data Normalization

Apollo Client會把執行過的query result儲存在Client Cache中,但假如說每個query中有對應到重複的Field時,單純的儲存每個Result可能會導致記憶體爆炸,Apollo會用reference的方式儲存每個query中的nested object。

Identify objects

可以看到下圖中的merchants和categories,因為屬於nested object所以被當作normalized object儲存在cache中

Generate caching IDs

預設的Caching ID是由__typenameid中間加上一個:組成

__typename:id

Replacing Object fields with references

這部分則是把原本Nested object的部分轉換成對應的reference

{
...
...
homePlan: {
a: ...
b: ...
}
}
---{
...
...
homePlan: {
__ref: <__typename:id>
}
}

Stored Normalized Object

要是取得的normalized object有一模一樣caching ID的,該Object則會進行merging的動作,Property相同則覆蓋上去,不同則加上去

以上四個行為是Apollo在把query儲存在cache中的一連串流程

Ways to update the Apollo client cache

1. Using readQuery and writeQuery

此方法需要給予一個完整的GraphQL query來讀取或寫入cache

This example creates (or edits) a cached Todo object with ID 5.

2. Using readFragment and writeFragment

藉由此方法,可以用cache提供的api來找尋到對應的normalized object

cache.identify(object)

可以看到上述的Id欄位需要給予一個Normalized object的unique id(default is __typename + id),只要在cache.identify中給予一個有組成該unique id的Object,即可對應到cache中的normalized object。

下列因為StoredQuery是由__typename + queryId組成uniqueId的,所以可以給予這樣的一個物件來返還在Cache中的reference

cache.identify({__typename: "StoredQuery", queryId: '615eabad0850c26452c68e75'})

3. cache.modify

直接對cache做操作,返還什麼值cache中的field就會被更改為哪些值,要注意的是modify 不能針對在cache中不存在的field進行寫入

Apollo Links

Apollo Links的主要功能蠻像axios interceptor或是Angular的Interceptor的,主要針對

  • 送入到server前的request進行處理,例如增加Header
  • 攔截response做一些前置處理,例如接收error並處理

如下圖所示,我們可以在宣告Apollo client時定義客製化的

Link Chain

What is a Link

Link是Apollo提供的一個類別,以下Apollo提供的範例建立一個客製化的Link

import { ApolloLink } from '@apollo/client';

const timeStartLink = new ApolloLink((operation, forward) => {
operation.setContext({ start: new Date() });
return forward(operation);
});

創造一個ApolloLink的Instance,並給予一個Callback function叫做request handler,該request handler最後須呼叫foward(operation)來對下一個Link做連接,唯一不需要呼叫該方法的Link叫做terminated link,即最後被呼叫的link。

Context

可以看到上述我們創造一個customLink時,有呼叫operation底下的setContext方法,該方法可以幫助我們設置context,可以視作link之中共同Share的一個物件來給後續的link做使用

結語

GraphQL雖然已經問世很久了,不過是最近才有機會有實戰的經驗,本篇算是寫給自己的筆記,目前負責的專案使用的State mgmt 工具為redux,而如果有使用Apollo Client的話,網路上有自己的一套State mgmt工具叫做apollo-link-state,把Apollo Client cache當作像redux一樣的資料集中地,這樣就可以完全的棄用redux把client cache當成single source of truth了,希望之後有機會玩看看

--

--