Godot から Supabase を使う
作ったもの
Supabase
の Database機能 と Realtime機能 でチャットを作りました
詳しくは以下のソース
Supabase とは
Open Source Project を使って作られたFirebaseの代わり
NoSQLではなくRDB(PostgreSQL)を使う
PostgREST
とかPhoenixChannel
(WebSocket)とかが使われてるらしい
詳しい構成は以下参照。
ローカルエミュレーターも用意されており、今回はこれを使った
npm install -g supabase
mkdir project && cd project
supabase init
supabase init
コマンドを実行するとAPI rootとAPI Key(public, private)が表示される。
後で使うのでメモっておく。
Godot Engine Supabase Addon
今回使った Supabase クライアントの Godot addon
AssetLib からインストールして、アドオンを有効にしておく
addons/supabase/.env
ファイルに、 supabase init
コマンド実行時に表示されたAPI root と API Key(public)を記入しておく。
[supabase/config]
supabaseUrl="http://localhost:8000"
supabaseKey="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJyb2xlIjoiYW5vbiJ9.36fUebxgx1mcBo4s19v0SzqmzunP--hm_hep0uLX0ew"
Godotで使えるSupabase UIライブラリもあるらしい(まだ触ってない)
Auth
Godot Supabase addon で Database や Realtime の機能を使うには、まずログインが必要。
ログイン時に取得できる AccessToken をアドオンモジュールに保存して使いまわしているっぽい。(Supabase.auth._auth
)
onready var email_edit = $VBoxContainer/Email/LineEdit
onready var password_edit = $VBoxContainer/Password/LineEdit
func try_login() -> SupabaseUser:
if GlobalState.user != null:
print("already login")
return null
var result: AuthTask = yield(Supabase.auth.sign_in(email_edit.text, password_edit.text), "completed")
if result.user != null:
GlobalState.user = result.user
return result.user
return null
func try_sign_up() -> SupabaseUser:
var result: AuthTask = yield(Supabase.auth.sign_up(email_edit.text, password_edit.text), "completed")
if result.user != null:
GlobalState.user = result.user
return result.user
return null
ログイン時に取得できるユーザー情報は後で使うので、自分で用意した GlobalState
Signleton に保存する。
Database
まず、チャットルームとチャットメッセージのテーブルをDBに作成する(SQLで)
supabase init
したときに PostgreSQL に接続できるURLが表示されるので psql
なり何なりお好きなDBクライアントを使って以下のSQLを実行。
CREATE TABLE public.rooms (
id serial NOT NULL PRIMARY KEY,
name text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT now()
);
CREATE TABLE public.chat_messages (
id serial NOT NULL PRIMARY KEY,
room_id integer NOT NULL REFERENCES public.rooms(id),
user_id uuid NOT NULL,
message text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT now()
);
PostgREST
により、テーブルを作成すると自動でCRUD APIが作成される。
HTTP Requestでも利用できるが、クライアントライブラリを使うと更に簡単。
func async_list_rooms() -> DatabaseTask:
var query = SupabaseQuery.new().from("rooms").select()
return Supabase.database.query(query)
func _ready():
var result: DatabaseTask = yield(async_list_rooms(), "completed")
if !result.error:
print(result.data)
rooms = result.data
update_rooms_ui(rooms)
else:
print("not login yet")
Godot Supabase addon に含まれる、SupabaseQuery
class はクエリビルダーとして使える。
Supabase.database.query()
メソッドの実行結果はTaskオブジェクトで、completed
シグナルを connect()
メソッドで登録したコールバックもしくはyield
で待ち受ける。
Realtime
Realtime機能は、Supabase.realtime
から利用できる。
# Listen Realtime Message Post
var client = Supabase.realtime.client()
var connect_result = client.connect_client()
if connect_result != OK:
print("realtime client error")
else:
# Websocket Client 接続に成功してからじゃないと channel を取得しようとしてもうまく行かない
client.connect("error", self, "_on_realtime_client_error")
channel = client.channel("public", "chat_messages", "room_id=eq.{room_id}".format({room_id=GlobalState.chat_room_id}))
channel.connect("insert", self, "_on_message")
channel.subscribe()
-
RealtimeClient
オブジェクトを取得 -
client.connect_client()
メソッドを実行 (signalにコールバックを登録するNode.connect()メソッドと似てて紛らわしい) -
client.channel()
メソッドで変更を購読するチャンネルを作成・取得する -
channel.connect()
でsignalにコールバックを登録 -
channel.subscribe()
で購読を開始
この手順通りにやらないとうまく行かないっぽい?ので注意
RealtimeChannel の topic
Supabase の Realtime機能は、内部的にはWebsocketの上にElixir
のPhoenixChannel
を使って実装されている(らしい)。
以下のようなtopicのフォーマットを指定する。
schema:table_name:column_name=op.val
例
-
public:rooms
- public.rooms テーブルの変更を購読する(INSERT, UPDATE, DELETE)
-
public:chat_messages:room_id=eq.8
- public.chat_messages テーブルの room_id カラムの値が 8 の列の変更だけ購読する
RealtimeClient.channel()
メソッドの3つの引数で指定してチャンネルを作成できる。
カラムの部分は省略できる。 *
ワイルドカードも使える。
まだ試していないこと
- Database機能のRow Level Security
- Storage機能
まだ調べてないこと
知ってる人とかいたらぜひコメントで教えて下さい
-
Firebase
とのパフォーマンスの差- Realtime Database と PostgreSQL の書き込み性能差
- Supabaseマネージドサービスだと、どのくらい自動スケールするのか?その場合の料金は?
- Supabase Realtime API の最大同時接続数とか応答速度パフォーマンスとか
- バックエンドがElixirだから並列性とか高そうな気がする
- WebSocketだから頻繁なレベルのリアルタイム性は厳しい気がする(UDPの用途とかのやつ)
感想
- すごく簡単だった
-
PostgREST
により、DBのテーブルがそのままAPIになるのが楽- NoSQLと違って、RDBのリレーションや制約を生かしたカッチリしたデータ構造を書ける
- SQL使える人はそのまま知識を活かせると思う
- Realtime API でDBの変更を監視できるのが便利
- Firebaseよりカスタマイズ性が高そう
ローカル開発ツールはdocker-composeを利用してるっぽく、簡単に起動・停止できるし、supabase ejectコマンドで Dockerfile を吐き出せるっぽい(まだ試してない)
カスタム構成にしたりセルフホスティングもできそう?
余談1
今まで、存在は知りつつも触ってなかったけど、PostgREST
みたいなDBのテーブルがそのままAPIになるというシステムは神だと思った。
普段の仕事でもバックエンドサーバー書いてて、単なるDB操作のためのAPIになってるってのはよくあるから、PostgREST
使えたら仕事なくなっちゃうかもなぁ笑(DB操作じゃないAPIもたまにはあるけど)
セキュリティ関連(Row Level Security)とかロール権限とかもうちょっと勉強して、仕事でも活かせたらなあと思うのであった
余談2
Firebase SDK の Godot addon もあった
Supabase は実績がちょっとね...という人は Firebase 使えばいいと思う
(そもそも Godot が Unity や Unreal Engine に比べて実績がないよね、というツッコミは置いといて)
Discussion