최근에 이직을 준비하며 이력서에 적어 놓은 apollo-client 관련해서 깊이 있는 질문을 다소 받았다. 익숙하게 쓰는 스택이어서 원리나 트레이드 오프 등에 대해서 생각을 많이 안 했던 것 같아서 복습해 볼 겸 클라이언트에서 graphql을 처리하는 도구들을 비교해 보는 글을 작성해 보려 한다.
Apollo Client
아폴로 클라이언트는 graphql로 로컬 및 원격 데이터를 관리할 수 있는 완전하게 기능을 제공하는 포괄적인 상태 관리 graphql 클라이언트이다. graphql 프로젝트가 오픈소스화된 직후인 2016년 meteor development group이 모든 프론트엔드 웹 프레임워크에 graphql 클라이언트 라이브러리를 구축할 계획으로 출시했다.
아폴로 클라이언트 라이브러리는 다음과 같이 리액트 앱에서 설치할 수 있다.
npm install --save @apollo/client graphql
설치 후 기본적인 쿼리를 하기 위해서는 다음 모듈을 설치해 주어야 한다.
import {
ApolloClient,
InMemoryCache,
gql
} from "@apollo/client";
그리고 나서 아폴로 클라이언트 인스턴스를 초기화 한다. 여기에 uri와 캐시 파라미터를 생성자에 넘겨준다.
const client = new ApolloClient({
uri: 'https://abc.com/graphql/',
cache: new InMemoryCache()
});
아폴로 클라이언트는 인메모리 캐시 인스턴스를 가진다.
그리고 나서 client.query()를 통해 데이터를 쿼리할 수 있다.
client.query({
query: gql`
{
missions {
name
website
}
}
`,
}).then(result => console.log(result));
다음 예제는 기본적인 쿼리를 나타낸 것으로 리액트에서는 더 많은 기능을 제공한다. 예를 들면 리액트 훅과의 직접적인 통합과 같은 기능이 있다.
아폴로 클라이언트의 장점과 단점은 다음과 같다.
장점
- 아폴로 클라이언트는 캐싱과 상태 관리 라이브러리 두 가지의 역할로 사용할 수 있다. 예를 들어 api로 호출한 데이터를 캐싱하고 싶을 때 아폴로 클라이언트를 사용하면 한 번에 해당 작업을 할 수 있다.
- 아폴로 클라이언트는 여러가지 기능을 지원한다. 예를 들면, 에러 처리나 페이지네이션, 데이터 프리패칭 등의 기능을 제공한다.
- 아폴로 클라이언트는 리액트, 뷰, 앵귤러, iOS, 안드로이드 등 여러 프레임워크를 지원한다.
- 아폴로 클라이언트 v3에서는 리액티브 변수들을 가지는 특징이 있다. 리액티브 변수를 사용하면 어플리케이션 어디에서나 접근할 수 있고 어떠한 타입으로도 저장할 수 있다는 장점이 있다. 지역 상태를 아폴로 클라이언트 캐시 바깥에서 표현할 때 유리한 메커니즘이 될 수 있다.
- 커뮤니티가 크고 견고해서 여기에서 지원을 많이 받을 수 있다. 오픈 소스화 되어 있는 서드 파티 라이브러리들도 많이 있다.
단점
- 번들 사이즈가 크다. 가장 작은 버전 기준으로 30.7KB 인데 이는 uRQL보다 2배 정도로 크다. 이러한 큰 번들 사이즈가 낮은 앱 성능을 유발할 수 있다.
- 리액트가 아닌 다른 프레임워크에서는 사용 가능하나 사용하기가 좀 불편할 수 있다.
- 캐싱과 관련해서 설정을 잘 해주지 않았을 때 불필요한 캐싱이 많이 일어나게 될 가능성이 있어 주의해야 한다. 참고
Relay
릴레이는 페이스북에서 2015년에 릴리즈한 graphql 클라이언트이다. 처음 릴리즈 된 이후 2017년 경에 더 직관적이고 최적화 된 방향으로 다시 릴리즈가 되었다.
릴레이를 리액트에서 사용하기 위해서는 필요한 패키지들이 있다. 릴레이는 크게 세 가지 정도의 주요 부분으로 이루어져 있다.
- 빌드 시간에 사용되는 컴파일러
- 코어 런타임
- 리액트 통합 레이어
다음 세 가지 패키지를 먼저 설치해준다.
npm install --save relay-runtime react-relay
npm install --save-dev relay-compiler graphql@^15.0.0 babel-plugin-relay
그리고 릴레이 컴파일러를 graphql 서버 스키마에 연결하기 위한 환경설정을 해 주어야 한다. 이를 위해 graphql 스키마를 graphql 서버에서 다운로드 받아서 .graphql 파일에 저장한다. 이를 도와주는 도구를 함께 설치한다.
npm install -g graphqurl
이제 graphql 스키마를 가졌다. package.json에 앱을 실행하거나 빌드 시, 컴파일러를 실행하는 명령을 추가해준다.
// <your-app-name>/package.json
{
...
"scripts": {
...
"start": "npm run relay && react-scripts start",
"build": "npm run relay && react-scripts build",
"relay": "npm run relay-compiler --schema schema.graphql --src ./src/ --watchman false $@"
...
},
...
}
릴레이 런타임과 환경을 설정한다. 이제 컴파일러가 설정이 되었고, 런타임에서 릴레이에게 graphql 서버에 연결을 어떻게 할 수 있는지 설정을 해 주어야 한다. /src 폴더 안에서 RelayEnvironment.js 파일을 만들고 다음 코드를 복사 붙여넣기 한다.
// <your-app-name>/src/RelayEnvironment.js
import { Environment, Network, RecordSource, Store } from "relay-runtime";
const store = new Store(new RecordSource());
const network = Network.create((operation, variables) => {
return fetch("https://abc.com/graphql/", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
query: operation.text,
variables
})
}).then(response => {
return response.json();
});
});
export default new Environment({
network,
store
});
이제 환경 설정이 되었고, 쿼리해서 데이터를 불러오는 로직을 작성해보자.
//App.js
import React from "react";
import { QueryRenderer, graphql } from "react-relay";
import environment from './RelayEnvironment';
const query = graphql`
query AppRepositoryMissionQuery{
missions {
name
website
}
}
`;
export default function Root() {
return (
<QueryRenderer
environment={environment}
query={query}
render={({ error, props }) => {
if (error) {
return <p>{error.message}</p>;
} else if (props) {
return <p>Mission name is {props.name} with website {props.website}</p>;
}
return <p>Loading your Relay Mordern data...</p>;
}}
/>
);
}
릴레이의 환경 설정이 아폴로 클라이언트에 비해 처음에는 조금 복잡하나, 릴레이는 한 번만 초기 세팅을 해주면 된다는 장점을 가지고 있다.
릴레이는 빌드 시에 graphql 코드를 분석하여, 런타임에서 유효성 검증, 변형, 최적화를 해준다. 이는 에러를 찾고 고치는 시간을 아껴주고 일련의 전체 과정을 부드럽게 해준다.
릴레이가 가진 장점을 더 알아보자.
- 릴레이는 높은 퍼포먼스를 위해 설계되었다. 앱이 커질수록 자동적으로 스케일한다. 증가하는 컴파일러는 반복 속도를 빠르게 처리하고 앞선 쿼리들의 시간을 최적화 해서 런타임에서 가장 효율적인 방식을 만든다.
- 릴레이는 graphql의 베스트 프랙티스를 적용하고 의존한다. 예를 들면, 릴레이는 전역적으로 유니크한 id값을 전체 스키마에서 의존하여 신뢰할 수 있는 캐싱과 데이터 프리패칭을 가능하게 한다.
- 릴레이는 선언적인 데이터패칭을 지원한다.
- 릴레이는 프론트엔드 어플리케이션의 실패 가능성을 줄여준다.
반면에 릴레이가 가진 단점은 다음과 같다.
- 릴레이는 환경 설정이 다소 복잡하고 번들 사이즈가 크다(27.7KB). 릴레이는 다소 독단적이며(highly opinionated) 엄격한 기준을 지키기 때문에 특정 컨벤션을 맞춰 주어야하고 이는 유연성을 떨어트린다. 러닝 커브도 높다.
- 릴레이는 리액트와 리액트 네이티브 어플리케이션에서만 사용이 가능하다.
그래서 무엇을 써야 하는가?
모든 도구가 그렇듯 정해진 답은 없다.
Structure(Performance) vs Freedom 의 선택 이라고 한 문장으로 요약할 수 있다.
아폴로 클라이언트는 여러 개발 환경에서 모든 기능을 지원하는 올 라운더(all-rounder) graphql 클라이언트를 필요로 할 때 적합하다. 러닝 커브가 낮고 커뮤니티가 활발해서 여러 도움을 받아 개발을 하기가 수월하다. 또한 유연하고 자유도가 높다.
릴레이는 큰 규모의 어플리케이션, 복잡한 데이터 요구사항과 수많은 의존성을 서로 간에 가진 경우 적합할 수 있다. 이러한 의존성을 직접 관리해주는 경우 시간도 많이 소요되고 에러가 발생하기 쉽다면 릴레이 사용이 도움이 될 수 있다. 구조화된 솔루션에 익숙한 팀이라면 릴레이 사용을 긍정적으로 검토해 볼 수 있다.
참고자료
https://hasura.io/blog/exploring-graphql-clients-apollo-client-vs-relay-vs-urql
https://graphqleditor.com/blog/graphql-with-react/
https://www.apollographql.com/docs/react/
'Web Frontend Developer' 카테고리의 다른 글
패스트캠퍼스 컨퍼런스인 캠프콘을 준비하며 (0) | 2024.03.30 |
---|---|
가상 DOM(Virtual DOM) 과 재조정(Reconciliation) 톺아보기 (1) | 2024.01.25 |
[FE] 도서 <우아한 타입 스크립트 with 리액트> 솔직 리뷰 (2) | 2023.11.25 |
react-hook-form & zod 로 복잡한 오브젝트 관리하기 (feat. immerjs) (0) | 2023.04.21 |
[Three.js] 3D 라이브러리를 사용해서 태양계 만들어 보기 (7) | 2023.03.10 |