0から作る個人開発アプリケーション:API Gateway編(HASURA)

はじめに

moai510.hatenablog.com

こちらのシリーズです。今回はAPI GatewayであるHASURAの部分の実装・構築について解説していきます。なお、HASURAには単なるAPI Gatewayではなく、Query系APIのハンドリングも任せています。

ここではHASURA自体の解説は省略するので、HASURAについて知らない方は公式ドキュメントを読んでください。

サンプルアプリ

moai510.hatenablog.com

こちらのサンプルアプリを対象として進めていきます。今回の作業後のコードは以下URLから確認できます。

GitHub - tonouchi510/application-arch-blueprint at hasura-work

事前準備

事前にFirebase Authenticationのセットアップをしてください。

HASURA側で、Firebase Authenticationで発行されたJWTトークンを検証し、そこに記録されたUIDやClaims内のRoleでアクセス制御を行うことを想定しています。

※HASURAではFirebase Authentication以外でも対応できますが、今回はこちらを使います。

ちなみに、Firebase Authenticationから発行されるトークンは、デフォルトではClaimsは空なので、Custom Claimsを設定しておく必要があります。ユーザ登録APIで一緒にできると良いです。

が、ここではまだAPIができていないので、動作確認も兼ねて、簡単にClaimsを設定するためのスクリプトも用意しておきました。

=> https://github.com/tonouchi510/application-arch-blueprint/blob/hasura-work/snippet/firebase/firebase_auth_command.py

こちらのスクリプト内にいくつかコマンドを用意しているので確認してみてください。例えばCustom Claimsを設定するには以下のコマンドを実行します。

$ python snippet/firebase/firebase_auth_command.py set-custom-claims --uid "XXXXXXX" --role "freemium"

これを実行した後に認証すれば、Custom Claims付きのJWTトークンが発行されることになります。HASURAではこれを使ってアクセス制御を実現します。

この後、具体的なFirebase AuthenticationとHASURAの連携方法についても書いていきます。

HASURAのセットアップ

今回は、ローカル開発環境のみ考えます。本番でHASURAを動かす話は別記事で書きます。

まずはHASURAが使えるようにセットアップが必要ですが、公式のDockerで動かすのがメインなのでほぼすることがないです。CLIのインストールだけしておきます。

Install / Uninstall the Hasura CLI | Hasura GraphQL Docs

HASURAプロジェクトの初期化

$ hasura init --admin-secret password

これだけでHASURAの起動に必要なファイルが揃います。作成されたhasuraフォルダに移動し、コンソールを起動できるはずです。

$ cd hasura
$ hasura console

docker-composeからの起動

ローカルで開発する際は、HASURA以外にもいくつかのコンテナを起動する必要があるので、docker-composeを使って起動することを想定しています。HASURAに関しても、公式が用意したDockerイメージに自分のHASURA設定ファイルをマウントして起動します。

また、HASURAを使うにはDBも必要なので、ここではPostgreSQLのコンテナも一緒に起動します。docker-compose.yamlは以下の通りです。

services:
  postgres:
    image: postgres:16.2
    ports:
      - "5432:5432"
    restart: always
    volumes:
      - ./snippet/init-db:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
      - TZ=TZ

  hasura-gateway:
    image: hasura/graphql-engine:latest.cli-migrations-v3
    ports:
      - "8080:8080"
    volumes:
      - ./backend/hasura/migrations:/hasura-migrations
      - ./backend/hasura/metadata:/hasura-metadata
    depends_on:
      - "postgres"
    restart: always
    environment:
      ## postgres database to store Hasura metadata
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:password@postgres:5432/postgres
      ## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs
      BACKEND_SERVICE_DB_URL: $DATABASE_URL
      ## enable the console served by server
      HASURA_GRAPHQL_ENABLE_CONSOLE: "false" # set to "false" to disable console
      ## enable debugging mode. It is recommended to disable this in production
      HASURA_GRAPHQL_DEV_MODE: "true"
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      # authentication
      HASURA_GRAPHQL_ADMIN_SECRET: password
      HASURA_GRAPHQL_JWT_SECRET: '{"type":"RS256","jwk_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com", "audience": "${FIREBASE_PROJECT_ID}", "issuer": "https://securetoken.google.com/${FIREBASE_PROJECT_ID}"}'
      # remote schema
      HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: "true"

ターミナルから以下を実行すれば起動できます。環境変数は適宜編集してください。

$ export FIREBASE_PROJECT_ID="XXXX"
$ export DATABASE_URL="postgres://postgres:password@postgres:5432/main"
$ docker compose up -d

コンテナ起動後は、先ほどの手順でHASURAコンソールを起動して各種設定変更などを行っていきます。

HASURAコンソール上で行う作業

データベースの接続

まずはHASURAで扱うデータベースを接続させる必要があります。手順としては以下の通りになります。

  • コンソール上でトップバーのDATAタブをクリック
  • Connect Database => Postgres を選択し、Connect Existing Databaseを選択
  • データベースの登録名や接続URLを記入して Connect Databaseをクリック
    • ここでは、Environment variableを選択し、docker-composeで設定したBACKEND_SERVICE_DB_URLを入力します

これでデータベースとの接続が完了しました。

データベースのテーブル作成

自分はテーブル作成、スキーマ編集などのDB操作もHASURAコンソール上でやることが多いです。

管理画面のような感じで使いやすいUIですし、マイグレーションファイルの統合や初期化、シードデータの投入・抽出などのコマンドもCLIで用意されているので、かなり便利です。また、後述しますが、どちらにしろGraphQLで扱えるようにするためにテーブルに関するリレーションやアクセス制御などを設定する必要があり、その意味でもDBに関する作業をHASURAコンソールでやるように統一しています。

先ほど登録したデータベースを選択して、Create Tableから作成できます。

こちらのブログで作ったER図を元に、テーブルを作成していってください。

0から作る個人開発アプリケーション:サンプルアプリ概要 - tonotech blog

ER図には反映されてないですが、外部キーやユニークキー、インデックスなどの設定も適宜行ってください。 作業が完了したら hasura/migrations フォルダに自動でマイグレーションファイルが作られているはずです。

ちなみに、UI上の作業ごとにマイグレーションファイルが作られてしまって細かいので、自分は本番運用前は0からマイグレーションファイルを作成し直しています。HASURAにはそれをやってくれる便利なコマンドもあって、以下のコマンドを実行することで最新のDBの状態から単一のマイグレーションファイルを生成してくれます。

$ hasura migrate create "init" --from-server

※initはマイグレーションファイルのサフィックス名。任意に編集してください。

テーブルのリレーション作成

HASURAでは、接続したデータベースのテーブルから自動で参照・更新のGraphQL APIを作成してくれます。そのため、この時点ですでに各テーブルのデータの参照・更新をクライアントに提供することができます(しかも柔軟なGraphQL API)。すごいですよね。

ただ、テーブルをまたがったクエリを実行するには、HASURA上でリレーションの設定が必要です(DBの外部キー設定しているだけではダメ)。テーブルごとに以下のタブでリレーションを追加することができます。追加自体は外部キーを設定してあれば自動で候補が出るので登録ボタンを押すだけで簡単です(ただし、忘れがちなので注意)。

実装上の注意点としては、GraphQLは便利なのでつい複雑なクエリ書いたり、色んなテーブルからデータを引っ張ってきちゃいがちですが、適切なIndex設計や、N+1問題などに注意しておく必要があります。

テーブルごとのアクセス制御の設定

次に、データへのアクセス制御の設定を行なっていきます。HASURAでは、認証情報を組み合わせてRoleごとに権限を設定することができます。

今回はFirebase Authenticationとの連携を考えます。セットアップは簡単で、docker-composeで書かれたこの環境変数を起動するコンテナに追加するだけでOKです。

HASURA_GRAPHQL_JWT_SECRET: '{"type":"RS256","jwk_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com", "audience": "${FIREBASE_PROJECT_ID}", "issuer": "https://securetoken.google.com/${FIREBASE_PROJECT_ID}"}'

これを設定しておくと、AuthorizationヘッダーのトークンをHASURAが検証して、トークンの中身をパースしてくれるようになります。

トークンは、事前準備で解説したようにカスタムクレームを設定し、以下のフォーマットで情報が記録されている必要があります。ここに記録されたx-hasura-〇〇を変数としてアクセス制御に使うことができます。

{
    "admin": True or False,
    "https://hasura.io/jwt/claims": {
        "x-hasura-default-role": {{ROLE}},
        "x-hasura-allowed-roles": {{ALLOWED_ROLES}},
        "x-hasura-user-id": {{UID}}
    }
}

具体的には、Permissionsタブで以下のように設定していきます(circlesテーブルの例)。

今回は更新系(insert, update, delete)はadmin以外は禁止(❌)し、参照系(select)のみ条件付きで使えるように設定しています。

条件は画像で設定されている通り、x-hasura-user-idつまりUIDが、owner_idcircle_members に含まれる場合に限定しています。

このように、アクセス制御も簡単に設定することが可能です。後からどういったアクセス制御の仕組みがあるかの確認も、コンソールから簡単に行えます。

Remote Schemaの追加

HASURAはAPI Gatewayとしても機能するため、HASURAの裏にバックエンドサービスを複数持つことも可能です。

バックエンドサービスとしては、GraphQL以外の任意のAPIサーバーを登録可能(その場合はActionとして登録する)ですが、ここでは同様に扱いやすいようにバックエンドサービスについてもGraphQL APIを提供するものを扱うことにします。

その場合は、Remote Schemasタブから登録することになるのですが、こちらはバックエンドサービスの実装編で解説しようと思います。

まとめ

これでローカルで開発する際にHASURAを扱う準備が整いました。ここまで書いてきている通り、参照系API(CQRSでいうQuery)については、HASURAで任せることでかなり工数削減になります。残りの更新系API(Command)についてはバックエンドサービスの実装編で解説するのでそちらも読んでもらえればと思います。

本番環境でもほとんど同じで、実行するコンテナでhasuraフォルダのメタデータマイグレーションを適用するだけですが、こちらは別の機会でまた解説すると思います。