schema.prisma에 message모델과 room 모델을 만든다. 메세지를 보낼 때 방이 만들어지도록 한다.
message를 subscription을 통해 실시간으로 확인한다.
subscription은 서버에 있는 것이 무엇이든 항상 들을 수 있도록 해준다.
어떤 일이 일어나고 내가 만든 조건을 충족하면 서버가 내게 어떤것을 push한다.
//pubsub.js
import { PubSub } from "apollo-server-express";
const pubsub = new PubSub();
export default pubsub;
// roomUpdate.typeDefs.js
import { gql } from "apollo-server-express";
export default gql`
type Subscription{
roomUpdate: Message
}
`;
// roomUpdate.resolvers.js
import { NEW_MESSAGE } from "../../constants";
import pubsub from "../../pubsub";
export default {
Subscription: {
roomUpdate: {
subscribe: () => pubsub.asyncIterater(NEW_MESSAGE)
}//message라는 event에 대해 subscribe한다.
}
};
http로 request를 하게 되면 subscription은 실시간으로 일어나는 일인데 http는 stateless해서 에러가 난다. http는 한번 request를 보내고 response를 받으면 끝나기 때문이다.
이 때 사용하는 것이 웹소켓이다. 웹소켓은 connection을 열고, 그 연결을 유지하고, 실시간으로 모든 것을 주고 받는다.
웹소켓의 프로토콜은 ws / wss이다.
서버상에 ws도 가능하도록 설치해줘야한다.
apollo Subscription reference를 참고하여 설치한다.
실시간을 사용하는 resolver에서 해당 data를 publish해준다.
// sendMessage.resolvers.js
const message = await client.message.create({
data: {
payload,
room: {
connect: {
id: room.id,
},
},
user: {
connect: {
id: loggedInUser.id,
},
},
},
}); //subscribe 함수명: roomUpdate
pubsub.publish(NEW_MESSAGE, { roomUpdate: { ...message } });
subscription은 큰규모의 object에 작지만 계속적으로 증가하는 변화가 있을 때나 저지연의 실시간 업데이트(ex. 채팅 앱)를 원할 때 사용하면 좋다.
위의 방식은 변화하는 값을 다 보게된다. 우리는 특정한 변화만 보고싶으므로 필터링을 한다.
withFilter의 첫번 째 인자는 어떤 걸 subscribe할 것인지 적는것이고 두번째 인자는 그 모델 중 조건에 맞게 필터링하는 함수이다.
//roomUpdate.resolvers.js
import { withFilter } from "graphql-subscriptions";
import client from "../../client";
import { NEW_MESSAGE } from "../../constants";
import pubsub from "../../pubsub";
export default {
Subscription: {
roomUpdate: {
subscribe: async (root, args, context, info) => {
const room = await client.room.findFirst({
where: {
id: args.id,
users: {
some: {
id: context.loggedInUser.id,
}
}
},
select: {
id: true
}
});
if (!room) {
throw new Error("You can't see this room");
}
return withFilter(
() => pubsub.asyncIterator(NEW_MESSAGE),
({ roomUpdate }, { id }) => {
return roomUpdate.roomId === id;
}//이 상태면 함수만 리턴한 것이기 때문에 호출까지 해줘야 한다.
)(root, args, context, info);
}
}
}
};
context에서 loggedInUser를 사용할 때 server.js에서 subscriptionServer를 만드는 곳에 아래 코드를 추가해야 한다. 왜냐하면 프로토콜이 달라서 토큰을 가져오는 방법이 다르기 때문이다.
// server.js
async onConnect({ token }) {
if (!token) {
throw new Error('Missing auth token!');
}
return {
loggedInUser: await getUser(token),
};
}