Hasura を用いたフルスタックアプリケーションのアーキテクトについて
Tweet現在開発中のアプリのアーキテクト
- Hasura => Hasura Cloud にホスティング。安い。早い。GCP に k8s 等を立ち上げるよりは圧倒的に安い。かつ、API 制限、パフォーマンス計測等、様々な機能が提供されている。
- PostgresDB => とりあえず安いので Heroku を使っている。お金があるなら GCP の Cloud SQL を使ってもいい。
- Auth0: 認証。慣れるまでに時間がかかるが、慣れればかなり開発効率がよい。かなり考えられている。
- Stripe: 決済。コード例が hooks を使っていたりとかなりモダン。また Hasura の webhook ベースの開発とも相性がよい。
- Pipeline の Apollo + Express Server => Vercel でホスティング。GCP の Cloud Run とかでもいいと思う。
- User Client の Apollo Client + React + TS => Vercel でホスティング。環境変数の管理等、Vercel はかなり優秀。CD/CI もになってくれているので楽。
Hasura には様々な機能があるが、実際の開発ではどう組み合わせればいいのか
Hasura には様々な機能があります。JWT もしくは Webhook を用いた Authentication、アクセスコントロール、Remote Schema の結合、Actions、それから Event Trigger や Schedule Trigger といったトリガー系。
まずこれらの機能を整理します。
Hasura の機能の分類と用途
-
認証系(以下のどちらか一方で認証をおこなう)
- JWT mode(Auth0, Firebase Authentication などサードパーティーが JWT を発行してくれる場合はこちら)
- Webhook mode(認証システムを自分で用意したい場合はこちら)
-
Hasura 経由で外部のエンドポイントを叩きたい場合(独自のロジックを実装したい場合にも、自分で立てたエンドポイントをこれらを使って叩くことで Hasura システムに取り込むことができる)
- Remote Schema(外部の GraphQL エンドポイントを叩きたい場合)
- Actions(外部の RESTful API エンドポイントを叩きたい場合)
-
イベントトリガー系
- Event Trigger データベースに変更があった場合に、対象の webhook に対してリクエストを発行する
- Cron Trigger 定期的に webhook に対してリクエストを発行する(例えば1日の終わりにデータを集計してテーブルを更新する場合等に用いる)
- One-off scheduled event 一回だけ webhook に対してリクエストを発行する(例えばサインインした後、2週間後にリマインドメールを送りたい場合に用いる)
アクセスコントロール
Hasura は JWT mode にせよ Webhook mode にせよ、認証機構を通して以下のようなアクセスコントロールようの情報を受け取る。これを用いてアクセスコントロールをおこなう。
認証: JWT mode / Webhook mode
基本的に認証は Saas を用いることが多い。Auth0, Firebase Auth 等があるが、Hasura で開発を始める場合に Firebase と連携する意味は大きくはないので、Auth0 をおすすめする。
その場合、認証は JWT Mode を用いる。
- 以下、ドキュメントも参照願う
- 環境変数の HASURAGRAPHQLJWT_SECRET に https://hasura.io/jwt-config/ で生成した key をセットする。この key を用いてフロントから送られてきた token を verify し、正しいものであれば parse し、アクセスコントロールようの情報が取得される。不正なトークンであれば弾かれる。
- Auth0 の rule で、token に含まれるアクセスコントロール情報を定義する。
- ユーザークライアントから、Auth0 の提供するログインページへリダイレクトし、認証する。正常に認証が終われば token を持った状態で元のページに戻される。その後 token を apollo client の Header に Authorization: Bearer
<Token>
として付与し Hasura に投げる。このあたりのユーザークライアントの設定はこのリポジトリを参照 - Hasura に投げられた token は HASURAGRAPHQLJWT_SECRET を用いて verify され、正しいトークンであれば parse され、auth0 の rule で定義したアクセスコントロール情報が取得できる。これをもとに Hasura は Operation を実行する。
ポイントはあくまで認証は auth0 に完全に任せる。それによって取得した JWToken から Hasura は「アクセスコントロール情報」をパースして使うだけ。その際 HASURAGRAPHQLJWT_SECRET を用いて token に不正がないかをチェックしているので安全。(token が違う場合にももちろん弾くし、token が改竄されている場合にも弾くことができる)
Webhook モードは基本的に新規開発では使わない
基本的には Saas を使うほうがセキュアであるため、Webhook モードで認証をする選択は第一の選択肢からは外していい。既存の認証システムを組み込む場合等に用いるケースがメインである。
Remote Schema / Actions
Hasura 経由で外部のエンドポイントを叩きたい場合にこれらを使うことができる。エンドポイントが GraphQL の場合は Remote schema を、REST の場合は Actions を使う。
また外部のエンドポイントは当然、自分で立てたサーバにすることもできるので、そこで独自のロジックを実装することで Hasura システムに取り込むことができる。
基本的には GraphQL サーバーをもう一つ立てて Remote Schema を使うのが良い。その Resolver から REST 叩くほうが堅牢であるため。
- Remote Schema(外部の GraphQL エンドポイントを叩きたい場合)
- Actions(外部の RESTful API エンドポイントを叩きたい場合)
具体的な用途としては、決済 Saas の Stripe の PaymentIntent の作成や、Auth0 からユーザー情報を取得したり変更したりといったロジックを Apollo Server 内で実装し、これを Remote Schema を使って Hasura に取り込む。取り込んでしまえばユーザークライントからは Hasura の一部として実行できる。(それが Hasura の内部にあるのか外部にあるのかは、隠蔽される)
イベントトリガー系
- Event Trigger データベースに変更があった場合に、対象の webhook に対してリクエストを発行する
- Cron Trigger 定期的に webhook に対してリクエストを発行する(例えば1日の終わりにデータを集計してテーブルを更新する場合等に用いる)
- One-off scheduled event 一回だけ webhook に対してリクエストを発行する(例えばサインインした後、2週間後にリマインドメールを送りたい場合に用いる)
なんにせよポイントは、Webhook なので対象は REST である点だ。なのでそれをうけるサーバーが必要になる。おすすめは apollo-server-express
を用いて GraphQL + REST のサーバーを立ててしまう方法だ。このサーバーの REST エンドポイントを webhook の対象とすればいい。
Cron Trigger を使って毎日ポイントが回復するゲームを作る
Cron trigger の具体的なユースケースとしては、ゲームアプリにおいて、ライフポイント等のなんらかのゲームを有利にするポイントを、1日のある時点で回復させる機能を実装することができる。例えば夜の23時に HP テーブルの値を変更する Cron Trigger をしこむことで実現できる。なおこれは Hausra に生えている API から実行することもできるので、GUI 経由ではなくプログラマブルに設定できる。
One-off scheduled event を使ってリマインドメールを実装する
One-off scheduled event の具体的なユースケースとしては、ユーザーサインインした二週間後に使い方のリマインドメールを送る、といったことができる。
Event Trigger が上記二つの昨日の実装の基盤になる
上記イベントトリガーは、基本的に DB に変更が入ったときに発行できる Event Trigger が実装の基盤となる。
例えばサインインの二週間後にメールをする機能であれば、One-off scheduled event 単体では実装できない。あくまで、Event Trigger を用いてユーザーが DB に追加されたことをトリガとして One-off scheduled event を発行するための webhook を叩き、その webhook が One-off scheduled event API を叩くことで発行する、というフローをとることになる。
一見煩雑に見えるが、これによって、ログを追うことが可能になり、障害に強い設計となる。詳しくはモダンアプリケーションの設計指針である 3 Factor App の Factor #2: Reliable eventing を参照。
各機能のユースケースの実際
Stripe の PaymentIntent を発行受け取りたいとき = Remote Schema
例えば商品購入を決定した場合、Stirpe を使っているのであればそれに対応する PaymentIntent というのを Stipe の SDK を叩いて取得する必要がある。その場合は、自分で立てた Apollo Server の Resolver で Stripe SDK を実行する。これを Remote Schema として取り込めばいい。query getPaymentIntent => resolver 内で Stripe SDK を叩いて値を取得 => これを返す
というパターンになる。
SaaS の webhook を受けて Hasura に operation を実行したい場合 = REST で受けて Hasura に Operation を発行
これも Stirpe を例にとるが、決済が完了したことを webhook を使って stripe は通知できるが、Hasura 本体にはそれを受ける REST エンドポイントはない。そのため自分で立てた REST エンドポイントで受ける。うけたら、そこから Hasura に対して operation を発行する。例えば在庫量の変更、商品の決済状況テーブルを変更する。
DB に変更があったことを Trigger として何かを実行したい場合 = Event Trigger から REST に webhook を発行
サインインした瞬間にメールを飛ばす実装。特定のテーブルの特定のカラムに対する変更をトリガーに webhook を起動させる。その webhook はメールを送信する。