GraphQLスキルを向上させる:リアルタイムサブスクリプション

この数年間、私は技術者が知的財産の価値を拡張することに集中することができるフレームワーク、製品、サービスを特定する試みをしてきました。これは私にとって素晴らしい旅であり、独特な学習機会で満ちています。

最近、私の技術的な側面は、以前に触れたことのある既存の概念に二次的な利点を見つけることができる状況があるか否かを考えました。つまり、私は元の親ソリューションが以前認識された同じレベルの影響を持つ別の利点を特定することができるか否かを考えました。

この記事では、GraphQLに深く取り組むことにしました。何か発見できるか探してみましょう。

RESTを休む時間が来たら」という記事で、GraphQLがRESTfulサービスより優先される実際のシナリオがあることを話しました。Apollo Serverを使用してGraphQL APIを構築・デプロイする方法を説明しました。

この続編記事では、GraphQLについての知識を一層深めることを計画しています。リアルタイムデータの取得に使用する購読を説明し、WebSocketサービスを構築して購読を消費する方法を学びます。

サマリ:顧客360の使用案例

前の記事は、顧客360の使用案例を中心にしていました。架空の事業所の顧客が以下のデータコレクションを維持します。

  • 顧客情報
  • 住所情報
  • 連絡方法
  • クレジット属性

グラフQLの使用において、大きな利点は、单一のGraphQLリクエストで顧客のトークン(ユニークなID)に必要なすべてのデータを取得することです。

JavaScript

 

type Query {
    addresses: [Address]
    address(customer_token: String): Address
    contacts: [Contact]
    contact(customer_token: String): Contact
    customers: [Customer]
    customer(token: String): Customer
    credits: [Credit]
    credit(customer_token: String): Credit
}

RESTfulアプローチを使用して顧客の一つの(360)ビューを取得するためには、複数のリクエストと応答を結合する必要がありました。GraphQLは、性能が大幅に改善される解決策を提供してくれます。

レベルアップのゴール

人生のどの方面でもレベルアップするためには、新しいゴールを達成する必要があります。私のゴールにおいては、これが意味します。

  • GraphQL内でのsubscriptions価値の価値を理解し、実装する
  • WebSocket実装を使用してGraphQLの購読を消費するWebSocket

GraphQL内でクエリと変更の上で購読を使用するアイデアは、以下の条件が満たされる場合に、最も適切な方法です。

  • 大きなオブジェクトに小さな、 Incremental changes
  • 低遅延な实时更新(例如、チャットアプリケーション)

これは重要です。なぜなら、GraphQL内で購読を実装することは簡単ではありません。サーバーの下層を更新するだけでなく、消費するアプリケーションの再設計も必要になります。

幸いなことに、私たちが追跡している顧客360の例は、購読に最適な場合です。また、それに対応するWebSocket方法を実装することにしました。

以前と同様に、私は今後もApolloを使用し続けます。

購読を使用してレベルアップするCreds

まず、Apollo GraphQL サーバーでの購読をサポートする必要のあるライブラリをインストールする必要があります。

Shell

 

npm install ws
npm install graphql-ws @graphql-tools/schema
npm install graphql-subscriptions 

これらのアイテムをインストールすると、私の元のリポジトリの `index.ts` を更新し、以下の `typedefs` 定数を拡張することに集中しました。

JavaScript

 

type Subscription {
    creditUpdated: Credit
}

また、新しい `PubSub` インスタンスを保持する定数を設定し、後で使用するサンプル購読を作成しました。

JavaScript

 

const pubsub = new PubSub();

pubsub.publish('CREDIT_BALANCE_UPDATED', {
    creditUpdated: {
    }
});

既存のリソーラーを整理し、この新しい用途に合わせて新しい `Subscription` を追加しました。

JavaScript

 

const resolvers = {
    Query: {
        addresses: () => addresses,
        address: (parent, args) => {
            const customer_token = args.customer_token;
            return addresses.find(address => address.customer_token === customer_token);
        },
        contacts: () => contacts,
        contact: (parent, args) => {
            const customer_token = args.customer_token;
            return contacts.find(contact => contact.customer_token === customer_token);
        },
        customers: () => customers,
        customer: (parent, args) => {
            const token = args.token;
            return customers.find(customer => customer.token === token);
        },
        credits: () => credits,
        credit: (parent, args) => {
            const customer_token = args.customer_token;
            return credits.find(credit => credit.customer_token === customer_token);
        }
    },
    Subscription: {
        creditUpdated: {
            subscribe: () => pubsub.asyncIterator(['CREDIT_BALANCE_UPDATED']),
        }
    }
};

その後、サーバーの設定を重构し、購読設計を導入しました。

JavaScript

 

const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
    server: httpServer,
    path: '/graphql'
});

const schema = makeExecutableSchema({ typeDefs, resolvers });
const serverCleanup = useServer({ schema }, wsServer);

const server = new ApolloServer({
    schema,
    plugins: [
        ApolloServerPluginDrainHttpServer({ httpServer }),
        {
            async serverWillStart() {
                return {
                    async drainServer() {
                        serverCleanup.dispose();
                    }
                };
            }
        }
    ],
});

await server.start();

app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
    context: async () => ({ pubsub })
}));

const PORT = Number.parseInt(process.env.PORT) || 4000;
httpServer.listen(PORT, () => {
    console.log(`Server is now running on http://localhost:${PORT}/graphql`);
    console.log(`Subscription is now running on ws://localhost:${PORT}/graphql`);
});

顧客が制御する更新をシミュレートするために、サービスが运行中の間に5秒ごとに50ドルをクレジット残高に追加する以下のメソッドを作成しました。残高が10,000ドルのクレジット限界を reached (または超え) すると、残高を2,500ドルにリセットし、クレジット支払いが行われたとしてシミュレートします。

JavaScript

 

function incrementCreditBalance() {
    if (credits[0].balance >= credits[0].credit_limit) {
        credits[0].balance = 0.00;
        console.log(`Credit balance reset to ${credits[0].balance}`);

    } else {
        credits[0].balance += 50.00;
        console.log(`Credit balance updated to ${credits[0].balance}`);
    }

    pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: credits[0] });
    setTimeout(incrementCreditBalance, 5000);
}

incrementCreditBalance();

全ての `index.ts` ファイルは ここ に見つかります。

Heroku にデプロイする

サービスが準備されたら、Interact できるようにサービスをデプロイする時間が来ました。先程と同様に、Heroku は最後に大丈夫で、私にとっては簡単に使用できるため、このアプロach を選びましょう。

始めるために、以下の Heroku CLI コマンドを実行する必要がありました。

Shell

 

$ heroku login
$ heroku create jvc-graphql-server-sub

Creating jvc-graphql-server-sub... done
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-sub.git

このコマンドは、Heroku が使用しているリポジトリの远处に自動的に追加しました。

Shell

 

$ git remote
heroku
origin

前の記事で述べたように、Apollo Server は本番環境で Apollo Explorer を無効にします。Apollo Explorer を私たちのニーズに合わせて利用可能にしておくために、NODE_ENV 環境変数を開発環境に設定する必要がありました。次の CLI コマンドでそれを設定しました:

Shell

 

$ heroku config:set NODE_ENV=development

Setting NODE_ENV and restarting jvc-graphql-server-sub... done, v3
NODE_ENV: development

コードを Heroku にデプロイする準備が整いました:

Shell

 

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

Heroku ダッシュボードを素早く確認すると、Apollo Server が問題なく稼働しているのが確認できました:

Settings セクションで、このサービスインスタンスの Heroku アプリ URL を見つけました:

https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/

  • 注意: この記事が公開される頃にはこのリンクは使用できなくなります。

その間、graphql をこの URL に付け加えることで、Apollo Server Studio を起動できました。これにより、サブスクリプションが期待通りに動作しているのが確認できました:

画面の右側にサブスクリプションのレスポンスが表示されています。

WebSocket スキルを向上させる

WebSocket サポートと Heroku の機能を活用して、作成したサブスクリプションを利用する実装を行うことができます。

私の場合、次の内容で index.js ファイルを作成しました。基本的には、WebSocket クライアントを作成し、クライアントが実行されていることを検証するためのダミーの HTTP サービスを確立しました:

JavaScript

 

import { createClient } from "graphql-ws";
import { WebSocket } from "ws";
import http from "http";

// Heroku の $PORT にバインドするダミーの HTTP サーバーを作成する
const PORT = process.env.PORT || 3000;
http.createServer((req, res) => res.end('Server is running')).listen(PORT, () => {
  console.log(`HTTP server running on port ${PORT}`);
});

const host_url = process.env.GRAPHQL_SUBSCRIPTION_HOST || 'ws://localhost:4000/graphql';

const client = createClient({
  url: host_url,
  webSocketImpl: WebSocket
});

const query = `subscription {
  creditUpdated {
    token
    customer_token
    credit_limit
    balance
    credit_score
  }
}`;

function handleCreditUpdated(data) {
  console.log('Received credit update:', data);
}

// creditUpdated サブスクリプションに登録
client.subscribe(
  {
    query,
  },
  {
    next: (data) => handleCreditUpdated(data.data.creditUpdated),
    error: (err) => console.error('Subscription error:', err),
    complete: () => console.log('Subscription complete'),
  }
);

完全なindex.jsファイルはここにあります。

この簡単なNode.jsアプリケーションをHerokuにデプロイすることもできます。既に使用していたHeroku app URLにGRAPHQL_SUBSCRIPTION_HOST環境変数を設定するように注意してください。

また、以下のProcfileを作成して、Herokuにより私のアプリをどのように起動するか指示しました。

Shell

 

web: node src/index.js

次に、新しいHeroku appを作成しました。

Shell

 

$ heroku create jvc-websocket-example

Creating jvc-websocket-example... done
https://jvc-websocket-example-62824c0b1df4.herokuapp.com/ | https://git.heroku.com/jvc-websocket-example.git

その後、GRAPHQL_SUBSCRIPTION_HOST環境変数を、動いているGraphQLサーバーに指しています。

Shell

 

$ heroku --app jvc-websocket-example \
    config:set \
GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql

この時点で、私のコードをHerokuにデプロイする準備が整いました。

Shell

 

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

WebSocketクライアントが始動した後、Herokuダッシュボードでその状態を確認することができます。

jvc-websocket-exampleインスタンスの内部のログを確認することで、jvc-graphql-server-subサービスのbalanceプロパティに対する複数の更新を見ることができます。デモでは、残高がゼロに減る場合をキャプチャーでき、支払いが行われたことをシミュレートできました。

ターミナルでは、CLIコマンドheroku logsを使用して、同じログを参照することができます。

Shell

 

2024-08-28T12:14:48.463846+00:00 app[web.1]: Received credit update: {
2024-08-28T12:14:48.463874+00:00 app[web.1]:   token: 'credit-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]:   customer_token: 'customer-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]:   credit_limit: 10000,
2024-08-28T12:14:48.463875+00:00 app[web.1]:   balance: 9950,
2024-08-28T12:14:48.463876+00:00 app[web.1]:   credit_score: 750
2024-08-28T12:14:48.463876+00:00 app[web.1]: }

GraphQLサービスがサブスクリプション実装を実行しているだけでなく、今度はWebSocketクライアントがこれらの更新を消費しています。

結論

私の読者は私の個人的な使命宣言を思い出しているかもしれません。これはITプロフェッショナルにも当てはまると感じます。

“知的財産の価値を拡張する機能を提供するために時間を集中してください。余分なことはフレームワーク、製品、サービスを利用しています。”

— J. Vester

このGraphQLの購読についての深入りの記事で、Heroku上で稼働しているApollo Serverから更新を消費することに成功しました。これは、Heroku上でも動いている他のサービスを使用しており、Node.jsベースのアプリケーションでWebSocketsを使用しています。軽量の購読を利用することで、変更しないデータのクエリを送信する必要はなく、クレジットバランスの更新を発生時に購読して受信するだけです。

序章で、私は以前に書いたトピックの中で追加の価値原則を探していると言っていました。GraphQLの購読は、私が考えていたものの良い例です。これにより、消費者はすぐに更新を受信することができ、元のデータに対するクエリを作成する必要はないからです。これにより、Customer 360のデータの消费者は非常に興奮していることになります。なぜなら、実際に起こっているときにライブ更新を受信できるということです。

Herokuは、私の使命声明に従っている另一の例です。CLIと標準的なGitコマンドを使用して迅速にプロトタイプ解決策を作成することができるプラットフォームを提供しています。これは、私の購読用途のショーケースを簡単に作成することができるだけでなく、WebSocketsを使用した消费者の実装もできます。

この記事のソースコードに興味がある場合は、GitLab上の私のリポジトリをご確認ください:

この取り組みを通じて、私はグラフQLのスキルを向上させる自信を持っています。この旅は新しく、私にとっては挑戦的で、とても楽しいものでした!

次には認証に取り組む予定です。これはグラフQLとApollo Serverをさらに向上させる機会になることを望んでいます。是非待ってください!

本当に素晴らしい一日を過ごしてください!

Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions