최근 GraphQL을 이용한 프로젝트를 진행한 적이 있다.
GraphQL에 관한 포스팅은 내 이전 velog를 확인하자.
https://velog.io/@jayson/project-개.고.수-반려동물을-위한-커뮤니티

RESTful API와 다른 고유의 특징을 가지고 있는 GraphQL은
Quary와 Mutation을 통해 요청을 보내 줄 수 있다.

대게 Quary는 DB에 변형을 주지 않는 요청을 보낼때 사용되고
Mutation은 DB에 변형을 일으키는 요청을 보낼 때 사용하지만,
절대적인 것은 아니다.

특히 Client에서 Mutation 요청을 보내 DB의 값이 변하고, 변한 값을 바로바로 Component에 적용시켜주기 위해서는 Mutation 내부에 옵션으로 refetchQuaries 를 추가해 주어 Mutation이 종료된 후에 다시금 해당 Component에서 Quary를 보내 변경된 DB를 읽어 오도록 할 수 있다.

"이런 개념이구나" 하고 프로젝트에서 로그인 화면을 진행하던 도중

어찌보면 당연하지만, 병렬로 배치된 컴포넌트의 Mutation에서 refetchQuaries를 실행하더라도 이웃한 컴포넌트의 Quary를 재 실행 시킬 수는 없다는 것을 확인했다.

// 병렬로 배치된 Headbar 컴포넌트와 컨텐츠가 담길 Tabs
<Headbar />
<div className="card-container">
    <Tabs type="card" defaultActiveKey="2">
        <TabPane tab="Report" key="1">
            {data.getMe.user === null ? (
                <AdvRescue />
            ) : data.getMe.user.admin !== false ? (
                <RescueAdmin />
            ) : (
                <Rescue />
            )}
        </TabPane>
        <TabPane tab="Album" key="2">
            <Album />
        </TabPane>
        <TabPane tab="Info" key="3">
            {/* Use map API and show near shops */}
            <Info />
        </TabPane>
    </Tabs>
</div>

위 : 병렬로 설계된 Main.tsx / 아래 : Headbar 컴포넌트를 바꿔주고 싶은 login.tsx

// login.tsx 에서 보내려'했던' refetchQuaries
<Mutation
    mutation={LOGIN_BUTTON}
    variables={{
        email: this.state.username,
        password: this.state.password,
    }}
    // refetchQuaries = {GET_ME}
>
    {(localLogin: any) => (
        <Form
            onSubmit={e => {
                this.handleSubmit(e, localLogin);
            }}
            className="login-form"
        >
            <Button
                type="primary"
                htmlType="submit"
                className="login-form-button"
            >
            Log in
            </Button>
        </Form>
    )}
</Mutation>
    // Quary가 보내준 response(data)에 따라 다른 상태를 보여주는 Headbar
      <Query<DATAS> query={GET_ME}>
        {({ loading, error, data }: any) => {
          if (loading) return <Loading />;
          if (error) {
            //console.log(error);
            return <Err />;
          }

          return (
            <Header>
              {data.getMe.err === null && data.getMe.user ? (
                <Menu>
                  <Menu.Item key="1" onClick={this.logoutClick}>
                    <a href="/">logout</a>
                  </Menu.Item>
                  <Menu.Item key="2">
                    mypage
                    <Link to={`/mypage/${data.getMe.user.id}`} />
                  </Menu.Item>
                </Menu>
              ) : (
                <Menu>
                  <Menu.Item key="2">
                    Login
                    <Link to={`/login`} />
                  </Menu.Item>
                </Menu>
              )}
            </Header>
          );
        }}
      </Query>

위의 Headbar 컴포넌트의 Quary는 GET_ME 라는 quary를 보내 자신이 로그인 상태인지 확인하여 로그인 상태일 경우 Menu의 구성이 바뀌는 코드이다.

login.tsx에서 refetch를 해 주지 않더라도, Mutation은 서버에 보내졌고, page를 refresh 할 경우, Headbar component는 제대로 변경이 되긴 하였다.

하.지.만.

로그인 버튼을 누르면 페이지가 변환되면서 refresh를 하지 않더라도 component의 상태를 변화 시키고 싶었지만 변경이 되지 않았다! (왜! 새로고침하면 잘 되는데! 왜!)
Mutation을 보내고 일부러 setTimeout도 걸어보고 refetchQuaries도 사용해 보았지만, 그럼에도 불구하고 Mutation이 실행 된 후 Headbar 컴포넌트의 상태는 변하지 않았다.

원인을 알아보던 중,
나는 그 이유가 Apollo가 기본적으로 지원하는 Cache 때문이란 것을 알았다.

프로젝트 당시에는 프로젝트의 빠른 진행을 위해 당장의 문제를 해결하기 위해 client.cache.reset(); 이라는 Cache를 삭제해 버리는 기능을 이용하였지만 프로젝트를 마친 후 Apollo가 가지고 있는 Cache의 기능적 특징이 무엇이 있는지 궁금해 졌다.

이에 가장 정확하고 확실한 공부법인 Document 정독하기를 실시하였고,
https://www.apollographql.com/docs/apollo-server/features/caching/
생각보다 어렵지 않게 Apollo가 지원하는 Cache의 기능들을 확인 할 수 있었다.

그 중에서는 Apollo가 단순히 Client에서만 코딩을 실시하는게 아니라
Server에서도 해당 Quary의 지속시간을 설정해 줄 수 있었다.

// Query from server
type Query {
    latestPost: Post @cacheControl(maxAge: 10)
}
// Query from Client
const resolvers = {
    Query: { post: (_, { id }, _, info) => {
        info.cacheControl.setCacheHint({ maxAge: 60, scope: 'PRIVATE' });
            return find(posts, { id });
        }
    }
}

이와 같은 방법을 통해, 로그인 요청이 있기 전에는 지속시간이 있는 Cache를, 요청을 받은 후에는 지속시간이 없는 refetchQuaries를 실행해 문제를 해결할 수 있지 않았을까 생각해본다.

이번 bloging을 통해 깨달은 가장 큰 교훈이 있다면
"급할수록 Docs참고" 가 아닐까 싶다.