최근 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참고" 가 아닐까 싶다.
quary ㅠㅠ 불편 ㅠㅠ