見出し画像

NestJS と Hasura で実現する Production GraphQL

こんにちは!ダイニーでソフトウェアエンジニアをしている kimujun です。

この記事は NestJS meetup Online #1 で発表した内容を記事化したものになります。

弊社では約 2 年ほど、NestJS と Hasura の組み合わせで GraphQL サーバーを本番運用しています。もっと多くの人にこの構成を知ってもらう & 使ってもらうため、どのような開発フローをとっているのかを紹介していきます💪

NestJS と GraphQL サーバー

NestJS では公式のパッケージから GraphQLModule が提供されており、これを使って簡単に GraphQL サーバーを立ち上げることができます。

実装としては Apollo GraphQL サーバーの実装をラップしているものになります。

以下のように main module で GraphQLModule を読み込みます。

main module で GraphQLModule を読み込む

読み込む際に introspection オプションをつけると、introspection 機能を同時に提供してくれます。

introspection とは、GraphQL サーバーにスキーマ情報を問い合わせる行為のことで、NestJS で GraphQLModule を利用する場合は /graphql のリクエストを投げることでこの機能を利用できます。

ただし、ただ単にこの機能を ON にするだけだと任意のリクエストに対しスキーマ情報を返してしまうため、セキュリティ上のリスクがあります。例えばこちらのような Apollo サーバーの認証に向けたライブラリなどを用いて上記のリスクを回避できます。

Code First & Schema First

公式ドキュメントで紹介されているとおり、NestJS で GraphQL サーバーを実装する場合 Code First と Schema First という 2 つのパターンで実装することができます。

Code First では、TypeScript のクラス定義からスキーマを自動で生成する形で実装します。

画像のようなデコレータ付きのクラスを定義することで、自動で SDL が生成されます。

Code First におけるスキーマの定義

SDL を事前に用意する必要がない点が Code First の利点です。これによって開発が効率化されます。

Schema First では、SDL を最初に書きそれに合わせてリゾルバーを実装します。

SDL を先に書き生成された型定義ファイルを参照してリゾルバーを実装する

Schema First の手法で開発するメリットとしては、実装前にスキーマを確定させておくことができる点が挙げられます。スキーマが実装前に確定していると、フロントエンド側とバックエンド側の開発に依存がなくなり、並列で開発を進めることができます。

ダイニーでは全てのエンジニアがフルスタックに開発しているため、上記のような Schema First のメリットが薄くなっています。後述する Hasura との組み合わせに際しても都合が良いため、ダイニーでは Code First の手法で開発をしています。

Hasura と Remote Schema

Hasura は DB に直接接続して GraphQL エンドポイントを提供するミドルウェアです。

https://hasura.io/ から引用

Hasura は DB のスキーマから自動でリゾルバーを定義・実装してくれるため、SDL の用意も必要ありません。

ダイニーではこの Hasura をフルに活用して開発・システム運用しており、Hasura なしでは回らないという状態です...!(詳しい活用方法についてはこちらをご覧ください)

Hasura の特徴の一つとして、マイグレーション機能が挙げられます。Hasura が提供するコンソールからスキーマを編集することで自動的にマイグレーションファイルが生成されるため、デプロイ時にはこのマイグレーションファイルをもとに DB のマイグレーションを行えます。

また、Hasura 外部の GraphQL スキーマを取り込み、マージする Remote Schema という機能もあります。

  • 複雑なビジネスロジックをクライアントに持たせたくない場合

  • パフォーマンスが必要な重いクエリを Hasura に任せたくない場合

などに有効です。スキーマのマージは指定した外部の GraphQL サーバーに対する introspection をもとに行います。

以上の通り、Hasura も簡単に GraphQL サーバーを利用するためのツールです。この Hasura を NestJS と組み合わせて利用することで、さらにパワフルな GraphQL 環境を実現できます🔥

NestJS と Hasura の接続

前段の Remote Schema として NestJS を指定することで、NestJS の GraphQL サーバーと Hasura の GraphQL サーバーを接続することができます。

これによって、複雑なビジネスロジックを実現したい場合やパフォーマンスが必要な場合の処理は NestJS に任せ、それ以外は Hasura に任せることができます。

「ダイニーのエンジニアリング3カ条」から引用

ダイニーでは、集計に関する Query、注文や会計に関する Mutation などを NestJS 側で実装しています。

開発フローは以下のようになります。

shop を作成するエンドポイントを作成するフロー

NestJS で GraphQL サーバーを作る利点

統一的な書き方で簡単に GraphQL サーバーを提供できる
Code First のアプローチで実装する場合、Controller の実装とほとんど同じ感覚で Resolver を書いていくことができます。もちろん module 単位の DI という NestJS そのものの特徴もフル活用して開発できます。

REST API との共存が可能。柔軟な使い方ができる
GraphQL Module を利用していながら通常の REST API も実装、共存させることができます。これによって、最初は REST API で実装 → GraphQL に徐々に移行、ということもできたりします。
この利点のおかげでダイニーのサーバーはいつの間にか GraphQL 環境になっていました😂

マイグレーションにまつわる課題

ダイニーでは NestJS サーバーで利用する ORM として TypeORM を採用しています。TypeORM では entity ファイルにスキーマ定義が記述されており、ここからスキーマ情報を読み出して動作します。

マイグレーションを Hasura に任せる場合、entity ファイルの更新を手動で行うためこの TypeORM のスキーマ定義と実際の DB のスキーマが乖離する恐れがあります。

TypeORM もマイグレーション機能を提供しており、これを利用する場合は乖離の恐れはありませんが、Hasura との併用の場合は Hasura にマイグレーションを任せたいため上記の問題が発生してしまうのです。

上記の問題を防ぐため、ダイニーでは CI によるスキーマのチェックを行っています。

  1. CI 上に立ち上げた DB サーバーに向けて Hasura のマイグレーションを行う

  2. TypeORM のマイグレーション機能を用いてマイグレーションファイルを生成する

  3. ファイルが生成されなければ Success、生成されれば Fail とする

shop テーブルに foo カラムを追加したが、entity ファイルの更新をしていない場合

まとめ

NestJS の GraphQLModule と Hasura を併用することで、簡単に可用性の高い GraphQL サーバーを作ることができます。ダイニーではこれらの組み合わせで高速に開発を行っています。
NestJS でサクサク実装しながら GraphQL サーバーを作ってみませんか?


ダイニーでは飲食店が大好きで業界に興味があるエンジニア、TypeScript が大好きなエンジニアを積極的に募集しています。
これからもダイニーは「また行きたい」と思えるような楽しくおもしろい飲食店を増やすべく飲食店来店客に新しい体験を生み出す開発やより多くの店舗に受け入れていただけるプロダクトになるための開発を進めていきます。少しでも興味を持っていただけた方は以下のリンクからカジュアル面談でお話させてください。

この記事が気に入ったらサポートをしてみませんか?