Apollo Client Subscription

hwakyungChoi·2021년 9월 21일
1

GraphQL 서버에서 실시간 업데이트 받기

Query 및 Mutation 외에도 GraphQL은 세 번째 작업 유형인 subscriptions을 지원합니다.

Query와 마찬가지로 subscriptions을 사용하면 데이터를 가져올 수 있습니다. Query와와 달리 subscriptions은 시간이 지남에 따라 결과를 변경할 수 있는 오래 지속되는 작업입니다. 이들은 대부분 WebSocket을 통해 사용자의 GraphQL 서버에 대한 활성 연결을 유지하여 서버가 subscriptions 결과에 업데이트를 푸시할 수 있도록 합니다.

Subscriptions은 새 개체 생성 또는 중요 필드 업데이트와 같은 백엔드 데이터의 변경 사항을 클라이언트에 실시간으로 통지하는 데 유용합니다.

Subscriptions을 언제 사용해야 하는지

대부분의 경우 클라이언트는 백엔드로 최신 상태를 유지하기 위해 subscriptions을 사용하지 않아야 합니다. 대신 queries를 사용하여 간헐적으로 폴링하거나 사용자가 관련 작업(예: 버튼 클릭)을 수행할 때 필요에 따라 queries를 다시 실행해야 합니다.

subscriptions은 다음과 같은 상황에서 사용해야 합니다.

  • 큰 객체에 대해 작고 점진적인 변화. 특히 대부분의 객체 필드가 거의 변경되지 않는 경우 대형 객체에 대해 반복적으로 폴링하는 데는 비용이 많이 듭니다. 대신 쿼리로 객체의 초기 상태를 가져오면 서버에서 개별 필드에 업데이트를 미리 적용할 수 있습니다.

  • 대기 시간이 짧은 실시간 업데이트. 예를 들어, 대화 어플리케이션의 클라이언트는 새 메시지가 사용 가능한 즉시 수신하기를 원합니다.

Subscription 정의

서버 측, 클라이언트 측 모두 queries와 mutations과 같이 subscription에 대해서 정의 합니다.

서버 측

GraphQL 스키마에서 사용 가능한 subscriptions을 subscriptions 유형의 필드로 정의합니다. 다음 commentAdded subscription은 특정 블로그 게시물에 새 설명이 추가될 때마다 subscriptions 클라이언트에 알립니다(postID 지정됨):

type Subscription {
  commentAdded(postID: ID!): Comment
}

클라이언트 측

애플리케이션의 클라이언트에서 Apollo Client가 실행할 각 subscription의 모양을 다음과 같이 정의합니다.

const COMMENTS_SUBSCRIPTION = gql`
  subscription OnCommentAdded($postID: ID!) {
    commentAdded(postID: $postID) {
      id
      content
    }
  }
`;

Apollo Client는 onCommentAdded subscription을 실행할 때 GraphQL 서버에 대한 연결을 설정하고 응답 데이터를 수신합니다. query와 달리 서버가 응답을 즉시 처리하고 반환할 것이라는 예상은 없습니다. 대신, 서버는 백엔드에서 특정 이벤트가 발생할 때만 데이터를 클라이언트로 푸시합니다.

GraphQL 서버가 구독 클라이언트에 데이터를 푸시할 때마다 해당 데이터는 query에서와 마찬가지로 실행된 subscription의 구조와 일치합니다.

{
  "data": {
    "commentAdded": {
      "id": "123",
      "content": "What a thoughtful and well written post!"
    }
  }
}

전송 설정하기

subscriptions은 일반적으로 영구 연결을 유지하므로 Apollo Client가 queries 및 mutations에 사용하는 기본 HTTP 전송을 사용하지 않아야 합니다. 대신, Apollo Client subscriptions은 일반적으로 커뮤니티에서 유지 관리하는 subscriptions-transport-ws 라이브러리를 통해 웹소켓을 통해 통신합니다.

1. 필요한 라이브러리 설치

Apollo Link 는 아폴로 클라이언트의 네트워크 통신을 사용자 지정할 수 있도록 도와주는 라이브러리입니다. 이 명령을 사용하여 작업을 수정하고 적절한 대상으로 라우팅하는 링크 체인을 정의할 수 있습니다.

WebSocket을 통해 subscriptions을 실행하려면 링크 체인에 WebSocketLink를 추가할 수 있습니다. 이 링크에는 subscriptions-transport-ws 라이브러리가 필요합니다. 다음과 같이 설치합니다.

npm install subscriptions-transport-ws

ApolloClient를 초기화할 수 있는 동일한 프로젝트 파일의 WebSocketLink 개체를 가져오고 초기화합니다.

import { WebSocketLink } from '@apollo/client/link/ws';

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/subscriptions',
  options: {
    reconnect: true
  }
});

3. 동작별로 통신 분할(권장)

Apollo Client는 WebSocketLink를 사용하여 모든 작업 유형을 실행할 수 있지만 대부분의 경우 query 및 mutation에 HTTP를 계속 사용해야 합니다. 그 이유는 query 및 mutation는 상태 저장 또는 장기간 연결이 필요하지 않으므로 WebSocket 연결이 아직 존재하지 않는 경우 HTTP의 효율성과 확장성이 향상됩니다.

이를 지원하기 위해 @apollo/client 라이브러리는 boolean 검사 결과에 따라 두 개의 서로 다른 링크 중 하나를 사용할 수 있는 split 함수를 제공합니다.

다음은 WebSocketLink와 HttpLink를 모두 초기화하여 이전 예제에서 확장한 예입니다. 그런 다음 split 함수를 사용하여 실행 중인 연산의 유형에 따라 하나를 사용하거나 다른 하나를 사용하는 단일 Link로 두 링크를 결합합니다.

import { split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { WebSocketLink } from '@apollo/client/link/ws';

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql'
});

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/subscriptions',
  options: {
    reconnect: true
  }
});

// The split function takes three parameters:
//
// * A function that's called for each operation to execute
// * The Link to use for an operation if the function returns a "truthy" value
// * The Link to use for an operation if the function returns a "falsy" value
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink,
);

이 로직을 사용하면 queries와 mutations는 HTTP를 정상적으로 사용하고 subscriptions은 WebSocket을 사용합니다.

4. Apollo Client에 링크 체인 제공

링크 체인을 정의한 후에는 링크 생성자 옵션을 통해 해당 체인을 Apollo Client에 제공합니다.

import { ApolloClient, InMemoryCache } from '@apollo/client';

// ...code from the above example goes here...

const client#### 5. WebSocket을 통한 인증(옵션) = new ApolloClient({

  link: splitLink,
  cache: new InMemoryCache()
});

링크 옵션을 제공하는 경우 uri 옵션보다 우선합니다(우리는 제공된 URL을 사용하여 기본 HTTP 링크 체인을 설정합니다).

5. WebSocket을 통한 인증(옵션)

subscription 결과를 수신하기 전에 클라이언트를 인증해야 하는 경우가 많습니다. 이렇게 하려면 다음과 같이 WebSocketLink 생성자에 connectionParams 옵션을 제공할 수 있습니다.

import { WebSocketLink } from '@apollo/client/link/ws';

const wsLink = new WebSocketLink({
  uri: 'ws://localhost:4000/subscriptions',
  options: {
    reconnect: true,

    connectionParams: {
      authToken: user.authToken,
    },
  },
});

WebSocketLink는 연결할 때마다 ConnectionParams 개체를 서버로 전달합니다. 서버에 WebSocket 연결을 수신하는 SubscriptionsServer 개체가 있는 경우, connectionParams를 수신하고 이 개체를 사용하여 다른 연결 관련 작업과 함께 인증을 수행할 수 있습니다.

Subscription 실행

Apollo Client의 useSubscription Hook을 사용하여 React에서 구독을 실행합니다. useQuery와 마찬가지로 useSubscription은 로드, 오류 및 UI 렌더링에 사용할 수 있는 데이터 속성을 포함하는 개체를 Apollo Client에서 반환합니다.

다음 예제 컴포넌트는 앞에서 정의한 subscription을 사용하여 지정된 블로그 게시물에 추가된 가장 최근의 주석을 렌더링합니다. GraphQL 서버가 클라이언트에 새 설명을 푸시할 때마다 컴포넌트는 새 설명과 함께 다시 렌더링됩니다.

const COMMENTS_SUBSCRIPTION = gql`
  subscription OnCommentAdded($postID: ID!) {
    commentAdded(postID: $postID) {
      id
      content
    }
  }
`;

function LatestComment({ postID }) {
  const { data, loading } = useSubscription(
    COMMENTS_SUBSCRIPTION,
    { variables: { postID } }
  );
  return <h4>New comment: {!loading && data.commentAdded.content}</h4>;
}

query에 대한 업데이트 Subscribing

query가 Apollo Client에서 결과를 반환할 때마다 해당 결과에는 subscribeToMore 함수가 포함됩니다. 이 기능을 사용하여 query의 원래 결과에 업데이트를 푸시하는 확인할 subscription을 실행할 수 있습니다.

subscribToMore 함수는 페이지 지정을 처리하는 데 일반적으로 사용되는 fetchMore 함수와 구조가 유사합니다. 주된 차이점은 fetchMore는 추적 쿼리를 실행하는 반면 subscribeToMore는 구subscription을 실행한다는 것입니다.

예를 들어, 주어진 블로그 게시물에 대한 기존 주석을 모두 가져오는 표준 query부터 시작하겠습니다.

const COMMENTS_QUERY = gql`
  query CommentsForPost($postID: ID!) {
    post(postID: $postID) {
      comments {
        id
        content
      }
    }
  }
`;

function CommentsPageWithData({ params }) {
  const result = useQuery(
    COMMENTS_QUERY,
    { variables: { postID: params.postID } }
  );
  return <CommentsPage {...result} />;
}

게시물에 새로운 설명이 추가되는 즉시 GraphQL 서버가 client에 업데이트를 전달하기를 원한다고 가정해 보겠습니다. 먼저 Comments_QUERY가 반환될 때 Apollo Client가 실행할 subscription을 정의해야 합니다.

const COMMENTS_SUBSCRIPTION = gql`
  subscription OnCommentAdded($postID: ID!) {
    commentAdded(postID: $postID) {
      id
      content
    }
  }
`;

다음으로 CommentsPage를 반환되는 CommentsPage 컴포넌트에subscribeToNewComments 속성에 추가하여 수정합니다.이 속성은 구성 요소가 마운트된 후 subscribeToMore 호출을 담당하는 기능입니다.

function CommentsPageWithData({ params }) {
  const { subscribeToMore, ...result } = useQuery(
    COMMENTS_QUERY,
    { variables: { postID: params.postID } }
  );

  return (
    <CommentsPage
      {...result}

      subscribeToNewComments={() =>
        subscribeToMore({
          document: COMMENTS_SUBSCRIPTION,
          variables: { postID: params.postID },
          updateQuery: (prev, { subscriptionData }) => {
            if (!subscriptionData.data) return prev;
            const newFeedItem = subscriptionData.data.commentAdded;
            return Object.assign({}, prev, {
              post: {
                comments: [newFeedItem, ...prev.post.comments]
              }
            });
          }
        })
      }
    />
  );
}

위의 예에서는 subscribeToMore에 다음과 같은 세 가지 옵션을 제공합니다.

문서는 실행할 subscription을 나타냅니다.
variables는 subscription을 실행할 때 포함할 변수를 나타냅니다.
updateQuery는 Apollo Client에 query의 현재 캐시된 결과(prev)를 GraphQL 서버에서 푸시하는 구독 데이터와 결합하는 방법을 알려주는 함수입니다. 이 함수의 반환 값은query에 대한 현재 캐시된 결과를 완전히 바꿉니다.
마지막으로, CommentsPage의 정의에서 subscribeToNewComments가 컴포넌트에 마운트될 때를 알려줍니다:

export class CommentsPage extends Component {
  componentDidMount() {
    this.props.subscribeToNewComments();
  }
}

useSubscription API 참조

참고: React Apollo의 Subscription 렌더 prop component를 사용하는 경우 아래에 나열된 옵션/결과 세부 정보가 여전히 유효합니다(옵션들은 prop component이며 결과는 렌더링 prop 함수에 전달됩니다). 유일한 차이점은 subscription prop(gql에 의해 AST 로 구문 분석된 GraphQL 구독 문서를 보관하는) 도 필요하다는 것이다.

옵션

useSubscription Hook은 다음 옵션을 사용할 수 있습니다.

  • subscription: GraphQL subscription 문서가 Graphql-tag를 사용하여 AST로 구문 분석되었습니다. subscription을 hook에 첫 번째 매개 변수로 전달할 수 있으므로 subscription hook를 사용할 경우 선택 사항입니다. subscription 구성 요소에 필요합니다.
  • variables : subscription에서 실행해야 하는 모든 변수를 포함하는 개체
  • shouldResubscribe : subscription을 해지하고 다시 subscription해야 하는지 여부를 결정
  • onSubscriptionData : useSubscription Hook/Subscription 구성 요소가 데이터를 수신할 때마다 트리거되는 콜백 함수를 등록할 수 있습니다. 콜백 옵션 개체 매개 변수는 클라이언트의 현재 Apollo Client 인스턴스와 subscriptionData의 수신된 구독 데이터로 구성됩니다.
  • fetchPolicy : 구성 요소가 Apollo 캐시와 상호 작용하는 방식
  • context : 구성 요소와 네트워크 인터페이스(Apollo Link) 간의 공유 컨텍스트입니다.
  • client : ApolloClient 인스턴스입니다. 기본적으로 useSubscription /Subscription은 컨텍스트를 통해 전달된 클라이언트를 사용하지만 다른 클라이언트를 전달할 수 있습니다.

결과

호출된 후, useSubscription Hook은 다음 속성들을 가진 객체를 반환합니다.

  • data : GraphQL subscription 결과를 포함하는 개체입니다. 기본값은 빈 개체입니다.
  • loading : 초기 데이터가 반환되었는지 여부를 나타내는 boolean
  • error : graphQLErrors과 networkError 속성을 가진 런타임 에러

0개의 댓글