koudenpaのブログ

趣味のブログです。株式会社はてなでWebアプリケーションエンジニアをやっています。職業柄IT関連の記事が多いと思います。

GraphQLのSubscriptionを試していてWebSocketが必ずしも要るとは限らないなぁと思うなど

最近GraphQLSubscriptionを試している。

GraphQLはクエリ言語で、そのスキーマどう実装するかは自由なわけだけれど、Subscriptionのようなサーバからクライアントへの通知の実装は「WebSocketでしょ! だからWebSocket使えないと厳しいよね」のような感覚を持っていた。しかし、いざ試してみるとそうでもないなという気になりつつある。

そんな気持ちをまとめた記事。

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じゃねぇ!

ならまぁSaaS試してみるか、とPusherを使ってみた。

ドキュメントに貼ってあったクライアントサイド実装がJavaScriptだったので、雑にTypeScriptにして動かした。

any だらけ!

Apollo for Pusher · GitHub

拍子抜けするくらい簡単に構成できて体験が良かった。

ただし金はかかる。

クラウドネイティブ的なGraphQL Subscription構成

クライアントサーバ間の継続した接続の確立をそのためのSaaSにオフロードする利点は、Webプリケーションのホスティングを任意な構成で行えるようになるところにある。

ステートレスなFaaSでもいいし、WebSocketをサポートしていない実行環境でもよい。

構成を考える自由度が上がってよいことだ。

また、専用のSaaSは専門部分の周辺要素が手厚い。Pusherなら接続状況のダッシュボードなどが充実している。運用もしやすいだろう。

ただし金がかかる。

クラウドSaaSは高い。

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はこの需要の選択肢にならんってことだぞ? 分かってるのか???