データベースがリアルタイム同期のロジックを持つ時代:SpacetimeDB活用ガイド
SpacetimeDBは、特にリアルタイム同期やマルチプレイヤー機能が必要なアプリケーション、特にゲーム開発において、非常に強力な味方になります。
SpacetimeDBは、一言で言うと「リアルタイムで同期するデータベース」です。
従来のデータベース(PostgreSQLやMySQLなど)が、クライアントからのリクエストに応じてデータを返したり保存したりするのに対し、SpacetimeDBは、データベースの状態が変化したとき、その変更を接続している全クライアントに自動的かつ即座に通知・同期します。
さらに、「スマートコントラクト」と呼ばれる機能を持っており、データベースに対する操作(データの追加、更新、削除)のロジックをデータベースのすぐそばで実行できます。これにより、サーバー側のロジックを信頼できる形で一元管理しやすくなります。
| メリット | なぜ役立つのか(従来の開発の課題解決) |
| リアルタイム同期が容易 | 自分でWebSocketや独自の同期ロジックを実装する手間が大幅に削減されます。クライアントは「データの変更を待つ」のではなく、「データベースの最新状態を常に反映する」ことが可能になります。 |
| 信頼できる状態の一元管理 | スマートコントラクト(Modと呼ばれるRustで書かれたロジック)のおかげで、データの整合性に関わる重要なビジネスロジックをデータベースレイヤーで実行できます。これにより、クライアント側や他のサーバーロジックでの「うっかりミス」や「チート行為」を防ぎやすくなります。 |
| 状態のロールバックとデバッグ | **「時間の巻き戻し(Spacetime)」**の機能により、データベースの状態を過去の任意の時点に簡単に戻せます。これは、バグの再現やデバッグ、ゲーム開発におけるリプレイ機能の実装などに非常に有用です。 |
| マルチプレイヤー開発の簡素化 | 特にマルチプレイヤーゲームでは、プレイヤー間の状態同期が非常に難しい課題ですが、SpacetimeDBがこれを抽象化してくれるため、エンジニアはゲーム自体のロジック開発に集中できます。 |
SpacetimeDBの導入は、大きく分けて「サーバー(データベース)側の準備」と「クライアント側の実装」の2段階になります。
サーバー側のロジックは、高速かつ安全なプログラミング言語Rustで記述します。
インストール
まず、SpacetimeDB CLI(コマンドラインインターフェース)をインストールします。
cargo install spacetimedb-cli
プロジェクト作成
新しいSpacetimeDBプロジェクトを作成します。
spacetimedb new my_game_project
cd my_game_project
スキーマ定義(schema.sdb) データベースに保存したいテーブルや、クライアントから呼び出せるスマートコントラクト(module::function)を定義します。これはSQLのCREATE TABLE文に似ています。
// schema.sdb
// プレイヤーの座標を保存するテーブル
table player_position {
id: u64,
x: f32,
y: f32,
}
// クライアントから座標更新のリクエストを受け付けるReducer(スマートコントラクト)
module game_logic {
fn move_player(ctx: ReducerContext, new_x: f32, new_y: f32);
}
ロジック実装(game_logic.rs)
定義したスマートコントラクトの中身(Rustのコード)を実装します。ここでは、クライアントから渡された新しい座標をデータベースに書き込む処理を記述します。
// src/game_logic.rs (Rust code)
use spacetimedb::{spacetimedb, ReducerContext};
// schema.sdbで定義したテーブル/Reducerをインポート
use crate::player_position;
#[spacetimedb(reducer)]
pub fn move_player(ctx: ReducerContext, new_x: f32, new_y: f32) {
// プレイヤーIDは、ログイン中のユーザーIDなどから取得するのが一般的ですが、
// サンプルとしてここでは固定のIDを使うと仮定します。
let player_id = 1;
// データベースを更新 (Upsert: あれば更新、なければ挿入)
player_position::upsert(player_position::Row {
id: player_id,
x: new_x,
y: new_y,
});
}
実行
開発用サーバーを起動します。
spacetimedb dev
クライアント(Webブラウザ、Unity、モバイルアプリなど)は、SpacetimeDBが提供するSDKを使ってサーバーに接続します。
SDKのインストール
npm install @clockwork-xyz/spacetimedb-sdk
接続とデータの購読
ここでは、クライアントが接続し、データベースの変更をリアルタイムで受け取るシンプルな例を示します。
// client.ts (TypeScript/JavaScript)
import { SpacetimeDBClient, SpacetimeDBTable, Row } from "@clockwork-xyz/spacetimedb-sdk";
// ※ローカルのスキーマ定義に基づいて自動生成される型ファイルをインポート
import { PlayerPosition, movePlayer } from "./generated_types";
const DB_URL = "ws://localhost:3000"; // 開発用サーバーのURL
async function main() {
// 1. クライアントを初期化
const client = new SpacetimeDBClient(DB_URL);
await client.connect();
console.log("SpacetimeDBに接続しました。");
// 2. データベースの変更を購読する
// PlayerPositionテーブルのRow型を定義
const playerTable = new SpacetimeDBTable<PlayerPosition>("player_position");
// データが更新・追加・削除されたときのコールバックを設定
playerTable.onUpdate((row: Row<PlayerPosition>, change: "insert" | "update" | "delete") => {
console.log(`[${change}] プレイヤー座標が更新されました: ID=${row.id}, X=${row.x}, Y=${row.y}`);
});
// 3. スマートコントラクト(Reducer)を呼び出す
// プレイヤーを新しい座標へ移動させる
// サーバー側のmove_player関数が実行されます
console.log("座標更新のリクエストを送信します...");
movePlayer(client, 10.5, 20.1);
// 4. 少し経ってから再度移動
setTimeout(() => {
console.log("再度、座標更新のリクエストを送信します...");
movePlayer(client, 50.0, 75.0);
}, 3000);
}
main().catch(console.error);
このコードを実行すると、movePlayerを呼び出すたびに、サーバー側のRustコードがデータベースを更新し、その更新の結果が即座にクライアント側のplayerTable.onUpdateコールバックに届き、コンソールに表示されるという流れになります。