Realtime GraphQL Subscriptions with Prisma

Taegyung Gong·2020년 12월 13일
2

GraphQL

목록 보기
2/3
post-thumbnail

About GraphQL Subscription...

GraphQL에서는 querymutation 그리고 subscription 총 3가지의 operation type이 있습니다.

이 중 실시간(real-time) 어플리케이션을 구현하기 위해 사용하는 subscription에 대해 이야기 해 볼 예정입니다. 😄

What is GraphQL Subscription?


Subscription은 발행/구독(pub/sub)의 모델을 따릅니다.
pub/sub 모델을 따르는 GraphQL의 subscription은 서버에서 발생하는 이벤트를 클라이언트에서 좀 더 효과적으로 인지할 수 있도록 해준다고 합니다.

HTTP 프로토콜 VS Web Socket 프로토콜

HTTP 프로토콜을 사용하는 query와 mutation과 달리 Subscription은 Web Socket 프로토콜을 사용한다고 합니다.

Web Socket Protocol
이벤트 방식으로 서버에서 발생하는 이벤트를 실시간으로 수신받을 수 있습니다. 따라서 빠른 데이터 전송이 필요한 앱에 사용하는데 적합합니다.

Before started...

DB Schema에 대해 간단하게 말씀드리고 시작하려 합니다.
본 예제는 채팅방에 채팅 내역을 실시간으로 확인하기 위해 공부 한 내용을 정리한 내용입니다.
따라서 Message, User, Room(채팅 방)의 구성으로 db table을 설계했다고 생각하시고 봐 주시면 감사하겠습니다! 😄


1.Setting Server

server.ts

import { GraphQLServer, PubSub } from 'graphql-yoga';
import schema from './schema'; //typedefs 와 resolver의 schema

const PORT = process.env.PORT || 4000;

const pubsub = new PubSub();

const server = new GraphQLServer({ schema, context: { pubsub } });

server.start({ port: PORT, cors: { origin: '*' } }, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

PubSub

GraphQL 서버에서 subscription을 구현할려면 Redis나 Google PubSub과 같은 pub/sub엔진이 필요합니다. 이를 graphql-yoga에서 PubSub엔진을 내장하고 있기 때문에 간단하게 pubsub 클래스를 임포트를 해줄 수 있습니다.

import { GraphQLServer, PubSub } from 'graphql-yoga';

const pubsub = new PubSub();

2. Mutation

createMessage resolver

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export default {
  Mutation: {
    createMessage: async (_, args, { pubsub }) => {
      const { text, userId, roomId } = args;
      const newMessage = await prisma.message.create({
        data: {
          text,
          user: {
            connect: {
              id: userId,
            },
          },
          room: {
            connect: {
              id: roomId,
            },
          },
        },
        include: {
          user: true,
        },
      });
      pubsub.publish(TRIGGER.NEW_MESSAGE, {
        newMessage,
      });
      return true;
    },
  },
};

context로 넘겨준 pubsub을 이용해서 새로 생성된 message를 publish합니다.
이벤트를 발생시킬 때 pubsub의 publish() 메서드를 사용해서 이벤트 이름과 이벤트 객체(newMessage)를 인자로 넘겨줍니다.

3.Subscription

Subscription typeDefs

type Subscription {
  newMessage(roomId: Int!): Message
}

subscription의 타입으로 스키마를 정의합니다. 구독하려는 채팅방의 roomId를 데이터로 받아 해당 채팅방의 Message를 반환타입으로 추가하였습니다.

Subscription resolver

import { withFilter } from 'graphql-subscriptions';

export default {
  Subscription: {
    newMessage: {
      subscribe: withFilter(
        (_: any, __: any, { pubsub }) => pubsub.asyncIterator('NEW_MESSAGE'),
        (payload, variables) => {
          return payload.newMessage.roomId === variables.roomId;
        },
      ),
    },
  },
};

asyncIterator()

subscription은 subscribe라는 속성을 갖는 객체를 필요로 합니다.
Server에서 context를 통해 넘겨준 pubsub을 통해 asyncIterator()메서드에 해당 이벤트를 넘겨주면 subscription은 해당 이벤트('NEW_MESSAGE')가 발생할 때마다 반응하게 됩니다.

withFilter

때때로 클라이언트는 컨텍스트와 인수를 기반으로 특정 이벤트를 필터링하기를 원할 것입니다.
이를 위해이 패키지의 withFilter 도우미를 사용하면 AsyncIterator를 필터 함수로 래핑하고 각 데이터를 선별할 수 있습니다. - Apollo 공식 문서

payload : 이벤트가 발생한 시점에서 넘겨준 data 즉 newMessage를 가지고 있습니다.
variables : typeDefs에서 넘겨 준 data 이 예제에서는 roomId를 가지고 있습니다.

return이 true일 경우만 해당 구독한 내용을 반환해주게 됩니다! 참 쉽죠? 😄


마치며...

실시간 채팅을 위해 서버를 구현하면서 느낀점은 subscription API를 설계하는 데 사전 지식만 있다면 아주 빠르고 쉽게 API를 생성할 수 있다는 생각이 들었습니다.
또한 Apollo 공식문서의 친절한 예제와 설명을 통해 filter 기능까지 쉽게 구현할 수 있었습니다.😄

아직 프로젝트를 진행하면서 기능 구현을 하는 과정에서 찾아보고 배운 내용을 정리하면서 작성하는 블로그이기 때문에 내용이 부족하거나 잘못된 내용이 있을 수 있습니다! 이점을 고려해서 너그럽게 봐주시면 감사하겠습니다. 🤭

다음에는 GraphQL의 각 resolver에서 Authentication을 구현한 것을 정리 해 볼 예정입니다.🔥

참고

Apollo 공식 문서
참고 블로그

profile
Web developer🐳

0개의 댓글