Etc

TIL | GraphQL Subscriptions

GraphQL Subscriptions

GraphQL Subscriptions?

  • 특정 이벤트가 발생할때 서버가 클라이언트로 데이터를 보낼 수 있도록 GraphQL에서 제공하는 기능이다.
  • 서버와 클라이언트가 실시간 연동을 하게끔 하기 위해 웹소켓을 기반으로 한다.
  • subscriptions을 사용함으로써, 일반적인 흔히 사용되는 request - response 통신에서 벗어날 수 있다. 대신 클라이언트에서 원하는 이벤트를 지정하여 서버와의 연결을 지속할 수 있다. 이후 해당 이벤트가 발생할때마다 서버는 클라이언트에 데이터를 전달한다.
  • 결론적으로, 실시간 데이터 통신을 위해 사용된다.(알람, 푸시, 배송상태 등에 적용할 수 있다.)

Setting with Apollo client

  • subscriptions 설정을 위해서는 graph-ws, subscriptions-transport-ws 두가지가 사용되지만 후자의 경우 유지보수가 액티브하게 되지 않으므로 전자를 추천한다고 한다. 하지만 HOW TO GRAPHQL 튜토리얼 흐름에 맞춰 후자의 라이브러리로 진행해보겠다.

Using

라이브러리 설치

yarn add subscriptions-transport-ws

ApolloClient instance 수정

  • 웹소켓을 사용할 때 subscriptionendpoint에서는 http 대신 ws을 사용하게 된다.
  • split은 특정 미들웨어 링크로 라우트하는데 사용된다.
// src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "src/styles/index.css";
import App from "./components/App";
import { split } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";

import {
  ApolloProvider,
  ApolloClient,
  createHttpLink,
  InMemoryCache,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { AUTH_TOKEN } from "./constants";

const httpLink = createHttpLink({
  uri: "http://localhost:4000",
});

// 웹소켓 연결을 위한 wslLink 추가
const wsLink = new WebSocketLink({
  uri: `ws://localhost:4000/graphql`,
  options: {
    reconnect: true,
    connectionParams: {
      authToken: localStorage.getItem(AUTH_TOKEN),
    },
  },
});

// 특정 미들웨어 링크로 라우트하는데 split이 사용됨
// 첫번째 인자가 true일 경우(즉 작업 타입이 query, mutation, subscription중 subscription일때), 두번째 인자로 주어진 링크에 라우팅: wsLink(웹소켓 통신)
// 첫번째 인자가 false일 경우, 세번째 인자로 주어진 링크에 라우팅: httpLink(일반 통신)

const link = split(
  ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind === "OperationDefinition" && operation === "subscription";
  },
  wsLink,
  httpLink
);

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

ReactDOM.render(
      <ApolloProvider client={client}>
        <App />
      </ApolloProvider>
  document.getElementById("root")
);

subscription 지정 및 사용

// feed list query
export const FEED_QUERY = gql`
  query GetFeedListQuery {
    ...
  }
`;

// feed subscription
export const NEW_LINKS_SUBSCRIPTION = gql`
  subscription {
   ...
  }
`;

// get data using subscription
const LinkList = () => {
  const { data, loading, error, subscribeToMore } = useQuery(FEED_QUERY);

  subscribeToMore({
    document: NEW_LINKS_SUBSCRIPTION,
    updateQuery: (prev, { subscriptionData }) => {
      if (!subscriptionData.data) {
        return prev;
      }
      const newLink = subscriptionData.data.newLink;
      const exists = prev.feed.links.find(({ id }) => id === newLink.id);

      if (exists) {
        return prev;
      }

      return Object.assign({}, prev, {
        feed: {
          links: [newLink, ...prev.feed.links],
          count: prev.feed.links.length + 1,
          __typename: prev.feed.__typename,
        },
      });
    },
  });
  return ...
};

결과

  • mutation으로 list가 실시간 추가됨을 통해, subscriptions 기능이 정상적으로 작동하고 있음을 알 수 있다.

Reference

'Etc' 카테고리의 다른 글

TIL | Reflow, Repaint  (0) 2022.08.28
TIL | 패키지 잠금파일  (1) 2022.03.26
TIL | Tailwind CSS  (0) 2022.03.10
TIL | Webstorm IDE Setting Share  (0) 2022.03.01
TIL | MSW(Mock Service Worker)  (0) 2022.02.07