最近GraphQLのSubscriptionを試している。
GraphQLはクエリ言語で、そのスキーマをどう実装するかは自由なわけだけれど、Subscriptionのようなサーバからクライアントへの通知の実装は「WebSocketでしょ! だからWebSocket使えないと厳しいよね」のような感覚を持っていた。しかし、いざ試してみるとそうでもないなという気になりつつある。
そんな気持ちをまとめた記事。
- WebSocket使えるか分からん
- WebSocketは欲しい感
- Laravelのライブラリの案内はSaaS
- クラウドネイティブ的なGraphQL Subscription構成
- LighthouseとPusherでのシーケンス
- お気持ち
WebSocket使えるか分からん
昨今のWebアプリケーションはいろんな構成でホスティングできる。
クライアントからWebアプリケーションが動作している場所とWebSocket接続を確立する間にどういう経路をたどるのかの構成パターンが多いということだ。
構成によってはWebSocket接続を確立できなかったり、お金が追加でかかったり、サーバサイドで構成に応じた追加処理をしなくてはならなかったり、スケールインで実行環境が消滅したりする。
正直面倒臭い。
AWSの場合だとこんな感じである。
WebSocketは欲しい感
とはいえ、メジャーなGraphQLサーバーの実装でまず提示されるのはWebSocketであるし、カジュアルにGraphQLサーバアプリケーションを実装するならWebSocketを使うのが平易だろうと思う。
Cloud RunとかApp Service等のコンテナランナーにサッとデプロイ、便利だと思う*1。
Subscriptions - Apollo GraphQL Docs もWebSocketが提示されている。
Laravelのライブラリの案内はSaaS
日頃手抜きホスティングを試行している自分が何でこんな記事を書くに至ったかというと、以下のダブルパンチから。
- 最近? 試しているApp RunnerがWebSocketサポートしてねぇ!
- 先にも挙げた通り
- 使ってるライブラリのプライマリな案内がWebSocketじゃねぇ!
- LaravelのGraphQLライブラリであるLighthouseのSubscriptionはクライアントサーバ間のリンクにPusherが案内されている
ならまぁSaaS試してみるか、とPusherを使ってみた。
ドキュメントに貼ってあったクライアントサイド実装がJavaScriptだったので、雑にTypeScriptにして動かした。
any だらけ!
拍子抜けするくらい簡単に構成できて体験が良かった。
ただし金はかかる。
クラウドネイティブ的なGraphQL Subscription構成
クライアントサーバ間の継続した接続の確立をそのためのSaaSにオフロードする利点は、Webプリケーションのホスティングを任意な構成で行えるようになるところにある。
ステートレスなFaaSでもいいし、WebSocketをサポートしていない実行環境でもよい。
構成を考える自由度が上がってよいことだ。
また、専用のSaaSは専門部分の周辺要素が手厚い。Pusherなら接続状況のダッシュボードなどが充実している。運用もしやすいだろう。
ただし金がかかる。
LighthouseとPusherでのシーケンス
どういう流れでSubscriptionを処理するのかの感覚を補強するのにSubscriptionの開始から終了までのシーケンス図を描いたので貼っておく。
sequenceDiagram box Client participant ApolloClient participant PusherLink participant HttpLink end box Server participant WebApplication participant DB end participant Pusher activate ApolloClient PusherLink->>Pusher: Connect activate PusherLink activate Pusher ApolloClient->>PusherLink: Subscribe PusherLink->>HttpLink: Bypass HttpLink->>+WebApplication: WebApplication->>DB: Create subscriber activate DB WebApplication->>Pusher: Create channel WebApplication-->>-PusherLink: Channel info PusherLink->+WebApplication: Authorize WebApplication-->-PusherLink: Signature for channel PusherLink->>Pusher: Subscribe channel loop Subscribing WebApplication->>+WebApplication: Some event WebApplication->>-Pusher: Push message Pusher->>PusherLink: Receive messsage PusherLink->>ApolloClient: ApolloClient->>+ApolloClient: OnData() ApolloClient->>-ApolloClient: HandleData() end ApolloClient->>PusherLink: Unsubscribe PusherLink->>Pusher: Unsubscribe channel Pusher->>+WebApplication: channel WebApplication->>-DB: Delete subscriber deactivate DB deactivate PusherLink deactivate Pusher deactivate ApolloClient
こういう風に処理しろよって仕様はあるが突き合わせてはいない。
https://spec.graphql.org/October2021/#sec-Subscription
大分割愛した図にしてしまっているが、この図中のPusherLink
(とサーバサイドの実装)を差し替えれば別なクライアントサーバ間リンク(例えばWebSocket)に切り替えられる。便利。
お気持ち
リアルタイム通信に関してもGraphQLよいね。
構成は多少考えどころがあるけれど、いざクライアントサーバ間の通信を確立してしまえば、後はSubscription操作を書けばサーバからの通知を受け取れてしまう。TypeScriptを使えば型もついて異様に開発体験が良い(記事中にその辺何も書いてないのは、何も考えずに実装できちゃったから……)。
そのクライアントサーバ間通信も、さほど手間をかけずに構成を切り替えるためのエコシステムが整っている。これってすごいことだと思う。
これでまたSignalRサービスから遠のいてしまった。本音ではMicrosoftに巻かれたいはずなんだが。
ややまとまりがないけれど、気持ちの整理記事なのでそういうものだろう。
*1:App Runnerはこの需要の選択肢にならんってことだぞ? 分かってるのか???