Logo

[GraphQL] React Apollo로 Subscription 호출

Subscription

GraphQL에는 query와 mutation 그리고 subscription 이렇게 총 3가지 operation type이 있습니다. 이 중에 query는 데이터 조회를 위해서 필수적으로 사용되고, mutation은 데이터 변경을 위해서 많이 사용되고 있습니다. query와 mutation 대비 다소 생소한 subscription은 주로 실시간(real-time) 애플리케이션을 구현하기 위해서 사용되는데요. subscription도 기본적으로 query처럼 데이터를 조회를 위해서 사용되지만 작동 방식에서 큰 차이가 있습니다.

query와 mutation은 전통적인 서버/클라이언트(server/client) 모델을 따르는 반면에, subscription은 발행/구독(pub/sub) 모델을 따릅니다. server/client 모델에서는 클라이언트에서 최신의 데이터를 받아오려면, 더 자주 서버를 호출하는 방법 밖에 없는데요. 접속자가 많은 서버에서 동시 다발적으로 변경이 발생하는 경우 클라이언트에서 아무리 자주 호출하더라도 완벽한 실시간을 달성하기는 어렵습니다. 또한, 변경이 자주 발생하지 않는 서버의 경우, 클라이언트에서 어렇게 자주 호출하는 것이 자체가 서버와 클라이언트 측 모두 낭비와 부담이 될 것입니다.

pub/sub 모델을 따르는 GraphQL의 subscription은 서버에서 발생하는 이벤트를 클라이언트에서 좀 더 효과적으로 인지할 수 있도록 해줍니다. query와 mutation이 HTTP 프로토콜을 사용하는 반면에, subscription은 Web Socket 프로토콜을 사용합니다. Web Socket을 사용하면 클라이언트는 서버와 연결 채널을 유지한체로, 서버에서 발생하는 이벤트를 실시간으로 수신받을 수 있습니다. 이번 포스팅에서는 React 용 Apollo Client를 사용하여 클라이언트 측에서 subscription을 어떻게 호출할 수 있는지 살펴보겠습니다.

패키지 설치

Apollo Client로 React 앱을 개발하기 위해서 우선 자신의 리액트 프로젝트에 관련 패키지를 설치해야합니다.

$ npm i install @apollo/react-hooks apollo-cache-inmemory apollo-client apollo-link-ws graphql graphql-tag subscriptions-transport-ws
  • package.json
  "dependencies": {
    "@apollo/react-hooks": "^3.1.3",
    "apollo-cache-inmemory": "^1.6.5",
    "apollo-client": "^2.6.8",
    "apollo-link-ws": "^1.0.19",
    "graphql": "^14.6.0",
    "graphql-tag": "^2.10.3",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "subscriptions-transport-ws": "^0.9.0"
  },

여기서 눈여겨 볼 점은 GraphQL의 subscription은 Web Soket 프로토콜을 사용하기 때문에 apollo-link-http 대신에 apollo-link-ws 패키지를 설치해줘야 한다는 점입니다. subscriptions-transport-ws 패키지는 apollo-link-ws 패키지가 필요로 하기 때문에 함께 설치가 되야합니다.

Apollo Client 생성

먼저, apollo-client 패키지에서 임포트한 ApolloClient 생성자를 이용해서 Apollo Client를 생성합니다. ApolloClient 생성자는 두 개의 옵션을 가진 객체를 인자로 받습니다. 첫번째, cache 옵션인데 가장 범용적으로 사용되는 InMemoryCache 객체를 설정합니다. 두번째, link 옵션으로는 WebSocketLink 클래스에 연동할 GraphQL API의 Web Socket 주소를 넘겨줍니다. 여기서는 이전 포스팅에서 개발한 GraphQL 서버의 URL을 endpoint로 사용하였습니다.

import ApolloClient from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { WebSocketLink } from "apollo-link-ws";

const wsLink = new WebSocketLink({
  uri: `wss://3wqzw.sse.codesandbox.io/graphql`,
  options: {
    reconnect: true,
  },
});

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: wsLink,
});

React에 Apollo Client 연결

다음으로, 위에서 생성한 ApolloClient 객체를 React에 연결해줘야 합니다. 앱 내에서 특정 React 컴포넌트만 GraphQL API 호출이 필요한 경우가 아닌 이상, 모든 React 컴포넌트에서 ApolloClient를 사용하도록 설정하는 것이 일반적입니다. 예를 들어, create-react-app으로 생성한 React 앱이라면 App.js 파일에서 이 작업을 해야합니다.

@apollo/react-hooks 패키지에서 임포트한 ApolloProvider 컴포넌트로 앱의 최상위 컴포넌트를 감싸줍니다. 이 때, ApolloProviderclient prop으로 위에서 생성한 ApolloClient 객체를 넘겨줘야 합니다. 이렇게 설정을 해주면, 앱 내의 모든 컴포넌트에서 GraphQL API 연동이 가능해집니다.

import React from "react";
import { ApolloProvider } from "@apollo/react-hooks";

import Timeline from "./Timeline";

function App() {
  return (
    <ApolloProvider client={client}>
      <h1>React Apollo Subscription</h1>
      <Timeline />
    </ApolloProvider>
  );
}

GraphQL Subscription 호출

자, 이제 GraphQL API를 상대로 messageAdded라는 subscription을 호출해보도록 하겠습니다.

먼저, graphql-tag 패키지에서 제공하는 gql이라는 template literal tag를 사용해서 일반 자바스크립트 문자열을 GraphQL 구문으로 바꿔줍니다. 그 다음, @apollo/react-hooks 패키지에서 임포트한 useSubscription React Hook 함수에 이 GraphQL 쿼리를 인자로 넘겨서 호출합니다. 그러면 useSubscription 함수는 응답 데이터(data) 뿐만 아니라, 로딩 여부(loading)와 오류 데이터(error)까지 함께 리턴합니다.

정상적으로 서버로 부터 응답 데이터가 도착하면 data로 부터 messageAdded 값을 읽어서 새로운 메시지를 웹페이지에 출력해주면 됩니다.

import React from "react";
import gql from "graphql-tag";
import { useSubscription } from "@apollo/react-hooks";

const MESSAGE_SUBSCRIPTION = gql`
  subscription onMessageAdded {
    messageAdded
  }
`;

export default function Timeline() {
  const { loading, error, data } = useSubscription(MESSAGE_SUBSCRIPTION);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;
  const { messageAdded } = data;
  return <p>New message: {messageAdded}</p>;
}

앱을 실행해보면 화면에 메시지가 1초에 한 번씩 계속 업데이트되는 것을 보실 수 있을 것입니다. 이것은 GraphQL의 일반적인 query 호출과 달리, subscription은 서버와의 연결을 유지한체로 계속해서 데이터를 수신하기 때문입니다.

전체 코드

화면에 계속 Loading... 이라고만 뜬다면 https://3wqzw.sse.codesandbox.io를 먼저 호출하여 GraphQL 서버를 활성화시켜주시기 바랍니다.

마치면서

이상으로 Apollo Client를 이용해서 React 앱에서 GraphQL subscription을 호출하는 방법에 대해서 알아보았습니다. GraphQL subscription을 특징을 잘 살려서 실시간 애플리케이션을 개발하는데 유용하게 잘 활용하셨으면 좋겠습니다.