LoginSignup
1
0

More than 1 year has passed since last update.

Next.js+URQL環境でPostgreSQLのPub/Subを使ったGraphQL Subscription

Last updated at Posted at 2022-12-20

Next.js + サーバーサイドTypeScript + 関数フレーバーでクリーンなアプリを作ったので実装意図とか書く Advent Calendar 2022
21日目株式会社mofmofに生息しているshwldです。

前日はMailgunでメーラーを実装するについて書きました

Next.jsでGraphQL Subscription環境を構築する

クライアントサイド (next-urql)

/apps/web/src/graphql/withGraphQLClient.tsx
withUrqlClient((_ssrExchange, _ctx) => ({
  url: `${API_HOST}/api/graphql`,
  exchanges: [
    dedupExchange,
    cache,
    fetchExchange,
    yogaExchange(),
  ],
})

source: /apps/web/src/graphql/withGraphQLClient.tsx

サーバーサイド

Pub/Subの実装

pg-pubsubを使っています。

今回PostgreSQLのPub/Subを使おうと思ったのは、インフラコスト削減が理由でした。
また、当初GraphQL Yogaを使っていなかったのもありました。
調べていると無料Redisも多少あるのと、途中でYogaに乗り換えたので、普通に実装するならRedisとYogaを使って実装するのが良い気がしています。

幸いPub/Subクライアントの実装をインフラ層で行っているので、乗り換えるのは簡単な構造になっています。

/infrastructures/db-pubsub/src/index.ts
import PGPubsub from 'pg-pubsub';
import type { Pubsub } from 'domain-interfaces';
import asyncify from 'callback-to-async-iterator';

const storyChannelName = (projectId: string) => `project-${projectId}-stories`;

export const createPubsubClient = (): Pubsub => {
  const pubsubInstance = new PGPubsub(process.env.DATABASE_URL);

  return {
    story: {
      subscribe({ projectId }) {
        return asyncify(async handler => {
          pubsubInstance.addChannel(storyChannelName(projectId), handler);
        });
      },
      publish(item) {
        pubsubInstance.publish(storyChannelName(item.object.projectId), item);
      },
    },
  };
};

source: /infrastructures/db-pubsub/src/index.ts

Subscribe

/use-cases/graphql-resolvers/src/modules/story/subscription-resolvers/story.update/subscribe-story-update.ts#L19-L28
const subscribed = context.pubsub.story.subscribe({
  projectId: args.projectId,
});
for await (const it of subscribed) {
  if (it.triggeredBy.id === context.currentUser.id) continue;

  yield {
    subscribeStoryUpdate: it.object,
  };
}

source: /use-cases/graphql-resolvers/src/modules/story/subscription-resolvers/story.update/subscribe-story-update.ts

Publish

/use-cases/graphql-resolvers/src/modules/story/mutation-resolvers/story.update/update-story.ts#L58-L60
context.pubsub.story.publish({
  object: story,
  triggeredBy: user,
})

source: /use-cases/graphql-resolvers/src/modules/story/mutation-resolvers/story.update/update-story.ts#L58-L60

次回予告

明日はバックグラウンドワーカーについて書きます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0