SpacetimeDBで始めるリアルタイム通信アプリケーション開発

## はじめに

リアルタイム通信を必要とするアプリケーションを開発する場合、従来はデータベースとサーバーを別々に準備し、それらの間の通信を管理するという複雑な構成が必要でした。この構成では、開発者はデータの整合性やリアルタイム性を担保するために様々な工夫を施す必要があります。

今回紹介する「SpacetimeDB」は、データベースとサーバーの機能を統合し、リアルタイム通信アプリケーションの開発を劇的に簡素化するツールです。特にゲーム開発などの低レイテンシが要求される場面で力を発揮します。SpacetimeDBはバージョン1.0に到達し、「Maincloud」サービスも提供開始されました。

<iframe width="560" height="315" src="https://www.youtube.com/embed/kzDnA_EVhTU?si=rWxBDpeYatSNStMm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

この記事では、SpacetimeDBの基本概念から、実際に簡単なチャット用のサーバーを構築するまでの流れを解説します。

## SpacetimeDBとは？

SpacetimeDBは「データベースでありながらサーバーでもある」新しいタイプのデータベースシステムです。従来のサーバー・クライアントモデルと異なり、アプリケーションロジックをデータベース内部で直接実行できます。

![SpacetimeDB基本アーキテクチャ](https://github.com/clockworklabs/SpacetimeDB/raw/master/images/basic-architecture-diagram.png)

**SpacetimeDBのアーキテクチャ**  

### 主な特徴

- **リレーショナルデータベース**: SQL互換のデータベースとして機能
- **モジュール**: データベース内で直接実行されるアプリケーションロジック（「モジュール」と呼ばれる）
- **リアルタイム同期**: クライアントとデータベース間でのリアルタイムなデータ同期
- **複数言語サポート**: サーバーサイドはRust、C#、クライアントサイドはRust、C#、TypeScriptをサポート
- **Unity連携**: Unityゲーム開発との連携が容易

### 従来のアーキテクチャとの違い

従来のアーキテクチャでは、以下のような構成が一般的でした：

1. クライアント（ブラウザ、モバイルアプリなど）
2. Webサーバー/ゲームサーバー
3. データベース

この構成では、クライアントからの要求がサーバーを経由してデータベースに届き、結果が同じ経路で返されます。これにより複雑性が増し、レイテンシも大きくなります。

SpacetimeDBでは：

1. クライアント（ブラウザ、モバイルアプリなど）
2. SpacetimeDB（データベース＋サーバー機能）

クライアントは直接SpacetimeDBに接続し、データベース内でアプリケーションロジックが実行されます。これにより開発の複雑性が低減し、レイテンシも改善されます。

## 主要な概念

SpacetimeDBを使いこなすために理解すべき主要な概念を紹介します。

### ホスト

SpacetimeDBの**ホスト**は、複数のデータベースを実行するサーバーです。自分でホストを運用することも、SpacetimeDBのクラウドサービスを利用することもできます。

### データベース

SpacetimeDBの**データベース**は、ホスト上で実行されるアプリケーションです。データベースは**テーブル**と**リデューサー**をエクスポートします。

### テーブル

SpacetimeDBの**テーブル**は通常のSQLデータベーステーブルです。モジュールのネイティブ言語で宣言されます。例えばC#では：

```csharp
[SpacetimeDB.Table(Name = "players", Public = true)]
public partial struct Player
{
    [SpacetimeDB.PrimaryKey]
    uint playerId;
    string name;
    uint age;
    Identity user;
}
```

### リデューサー

**リデューサー**はデータベースによってエクスポートされる関数で、クライアントはこれを呼び出してデータベースと対話します。一種のリモートプロシージャコールです。

Rustでは以下のように記述できます：

```rust
#[spacetimedb::reducer]
pub fn set_player_name(ctx: &spacetimedb::ReducerContext, id: u64, name: String) -> Result<(), String> {
   // ...
}
```

### アイデンティティ

SpacetimeDBの`Identity`は、モジュールと対話する誰かを識別します。長期間有効な、公開されたグローバルな識別子で、異なる接続間でも同じエンドユーザーを参照します。

### 状態ミラーリング

SpacetimeDBは、データベースからクライアントアプリケーションへの状態の**自動ミラーリング**をサポートしています。クライアントが興味を持つ情報を指定するSQLクエリを書くと、SpacetimeDBは関連するテーブルの型をクライアント言語で生成し、データベースの状態が変化するたびにクライアントにライブ更新のストリームを提供します。

## インストールと基本的な使い方

SpacetimeDBを始めるには以下の手順に従います：

1. SpacetimeDB CLIのインストール
2. スタンドアロンノードの起動
3. モジュールの作成とアップロード
4. クライアントライブラリを使用した接続

### SpacetimeDB CLIのインストール

SpacetimeDB CLIは複数の方法でインストールできます。公式サイトからの最新のインストール手順をここで紹介します。

#### macOSの場合

macOSへのインストールは非常に簡単です。以下のインストールスクリプトを実行するだけです：

```bash
curl -sSf https://install.spacetimedb.com | sh
```

インストール後は`spacetime`コマンドを使用してバージョン管理ができます。

#### Linuxの場合

Linuxへのインストールも同様に簡単です。以下のインストールスクリプトを実行します：

```bash
curl -sSf https://install.spacetimedb.com | sh
```

インストール後は`spacetime`コマンドを使用してバージョン管理ができます。

#### Windowsの場合

Windowsでは、以下のスニペットをPowerShellに貼り付けるだけでインストールできます：

```powershell
iwr https://windows.spacetimedb.com -useb | iex
```

WSL（Windows Subsystem for Linux）を使用する場合は、Linux向けのインストール手順に従ってください。

#### Dockerを使用する方法

コンテナ内でSpacetimeDBを実行したい場合は、以下のコマンドを使用できます：

```bash
docker run --rm --pull always -p 3000:3000 clockworklabs/spacetime start
```

#### ソースからのインストール

MacOSとLinuxでは以下の手順でソースからインストールできます：

```bash
# rustupのインストール（cargoとwasm32-unknown-unknownターゲットがすでにインストールされている場合はスキップ可能）
curl https://sh.rustup.rs -sSf | sh
# SpacetimeDBのクローン
git clone https://github.com/clockworklabs/SpacetimeDB
# CLIのビルドとインストール
cd SpacetimeDB
cargo install --path ./crates/cli --locked
```

### スタンドアロンノードの起動

SpacetimeDBスタンドアロンサーバーを起動するには：

```bash
spacetime start
```

デフォルトではポート`3000`でリッスンします。**重要**: モジュールの公開や呼び出しを行う前に、必ずサーバーが起動していることを確認してください。

サーバーが起動していない状態でコマンドを実行すると、以下のようなエラーが発生します：

```
Error: error sending request for url (http://127.0.0.1:3000/v1/database/...)
Caused by:
    0: client error (Connect)
    1: tcp connect error: Connection refused (os error 61)
    2: Connection refused (os error 61)
```

このエラーが表示された場合は、別のターミナルウィンドウで`spacetime start`コマンドを実行してサーバーを起動してから、再度操作を試みてください。

## クイックスタート: チャットアプリケーションの構築

SpacetimeDBを使った簡単なチャットアプリケーションを構築してみましょう。このチュートリアルは[公式ドキュメント](https://spacetimedb.com/docs/modules/rust/quickstart)から引用・翻訳し、一部拡張しています。

SpacetimeDBモジュールは、WebAssemblyバイナリにコンパイルされ、SpacetimeDBにアップロードされるコードです。このコードは、SpacetimeDBリレーショナルデータベースと直接インターフェースするサーバーサイドロジックになります。

各SpacetimeDBモジュールは、テーブルとリデューサーのセットを定義します：

- テーブルは、`#[table(name = table_name)]`でアノテーションされたRust構造体として定義
- リデューサーは、`#[reducer]`でアノテーションされた関数として定義

### 1. プロジェクト構造の作成

```bash
mkdir quickstart-chat
cd quickstart-chat
spacetime init --lang rust server
```

### 2. テーブルの定義

`server/src/lib.rs`にテーブルを定義します：

```rust
use spacetimedb::{table, reducer, Table, ReducerContext, Identity, Timestamp};

#[table(name = user, public)]
pub struct User {
    #[primary_key]
    identity: Identity,
    name: Option<String>,
    online: bool,
}

#[table(name = message, public)]
pub struct Message {
    sender: Identity,
    sent: Timestamp,
    text: String,
}
```

### 3. リデューサーの実装

ユーザー名を設定するリデューサー：

```rust
#[reducer]
/// クライアントがユーザー名を設定するためのリデューサー
pub fn set_name(ctx: &ReducerContext, name: String) -> Result<(), String> {
    let name = validate_name(name)?;
    if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
        ctx.db.user().identity().update(User { name: Some(name), ..user });
        Ok(())
    } else {
        Err("Cannot set name for unknown user".to_string())
    }
}

/// 名前が受け入れ可能かチェックする関数
fn validate_name(name: String) -> Result<String, String> {
    if name.is_empty() {
        Err("Names must not be empty".to_string())
    } else {
        Ok(name)
    }
}
```

メッセージを送信するリデューサー：

```rust
#[reducer]
/// クライアントがメッセージを送信するためのリデューサー
pub fn send_message(ctx: &ReducerContext, text: String) -> Result<(), String> {
    let text = validate_message(text)?;
    log::info!("{}", text);
    ctx.db.message().insert(Message {
        sender: ctx.sender,
        text,
        sent: ctx.timestamp,
    });
    Ok(())
}

/// メッセージのテキストが受け入れ可能かチェックする関数
fn validate_message(text: String) -> Result<String, String> {
    if text.is_empty() {
        Err("Messages must not be empty".to_string())
    } else {
        Ok(text)
    }
}
```

クライアント接続・切断処理：

```rust
#[reducer(client_connected)]
// クライアントがSpacetimeDBに接続したときに呼ばれる
pub fn client_connected(ctx: &ReducerContext) {
    if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
        // 既存ユーザーの場合はオンライン状態を更新
        ctx.db.user().identity().update(User { online: true, ..user });
    } else {
        // 新規ユーザーの場合は新しいレコードを作成
        ctx.db.user().insert(User {
            name: None,
            identity: ctx.sender,
            online: true,
        });
    }
}

#[reducer(client_disconnected)]
// クライアントがSpacetimeDBから切断したときに呼ばれる
pub fn identity_disconnected(ctx: &ReducerContext) {
    if let Some(user) = ctx.db.user().identity().find(ctx.sender) {
        ctx.db.user().identity().update(User { online: false, ..user });
    } else {
        log::warn!("Disconnect event for unknown user with identity {:?}", ctx.sender);
    }
}
```

### 4. モジュールの公開

モジュールを公開する前に、SpacetimeDBはGitHub認証を使用して身元確認を行います。以下のコマンドを実行すると、GitHub認証画面が表示されます：

```bash
spacetime publish --project-path server quickstart-chat
```

GitHub認証プロセスでは、「SpacetimeAuth by Clockwork Labs」が以下の権限を要求します：

![Screenshot 2025-03-06 at 16.29.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79562/6fc3ccc8-b2ac-4366-8ee9-7eb83c291d11.png)

- GitHubアイデンティティの確認: あなたのGitHubアカウントが実在するかの確認
- アクセス可能なリソースの確認: 基本的なアカウント情報へのアクセス
- ユーザーに代わって行動する権限: 認証トークンの発行など
- メールアドレスの閲覧: 連絡先の取得

認証に成功すると、モジュールがローカルのSpacetimeDBインスタンス（`http://127.0.0.1:3000`）にアップロードされます。

公開が成功したら、以下のコマンドでデータベースのリストを確認できます：

```bash
spacetime list
```

このコマンドを実行すると、公開されたすべてのモジュール（データベース）とそのIDが表示されます。この情報は後で特定のデータベースを操作する際に役立ちます。

#### トラブルシューティング: よくあるエラー

##### `edition2024` エラー

以下のようなエラーが表示された場合：

```
error: failed to download `spacetimedb-data-structures vX.X.X`
Caused by:
  feature `edition2024` is required
  The package requires the Cargo feature called `edition2024`, but that feature is not stabilized in this version of Cargo...
```

これは、SpacetimeDBが最新のRust機能である`edition2024`を必要としていることを示しています。このエラーを解決するにはRustのnightly版を使用してみましょう：

```bash
# nightlyツールチェーンをインストールして使用
rustup toolchain install nightly
cd server
rustup override set nightly

# 再度公開を試みる
cd ..
spacetime publish --project-path server quickstart-chat
```

##### WebAssemblyターゲットエラー

以下のようなエラーが表示された場合：

```
error[E0463]: can't find crate for `std`
  |
  = note: the `wasm32-unknown-unknown` target may not be installed
  = help: consider downloading the target with `rustup target add wasm32-unknown-unknown`
```

これは、WebAssemblyターゲットがインストールされていないことを示しています。このエラーを解決するには：

```bash
# 標準版Rustを使用している場合
rustup target add wasm32-unknown-unknown

# nightly版Rustを使用している場合
rustup target add --toolchain nightly wasm32-unknown-unknown

# 再度公開を試みる
spacetime publish --project-path server quickstart-chat
```

### 5. リデューサーの呼び出しとテスト

CLIからリデューサーを呼び出してテストできます。**重要**: リデューサーを呼び出す前に、必ずSpacetimeDBサーバーが起動していることを確認してください（`spacetime start`コマンドで起動）。

```bash
spacetime call quickstart-chat send_message 'Hello, World!'
```

呼び出し時に以下のようなエラーが発生する可能性があります：

1. サーバーが起動していない場合の接続エラー：

```
Error: error sending request for url (http://127.0.0.1:3000/v1/database/quickstart-chat/identity)
Caused by:
    0: client error (Connect)
    1: tcp connect error: Connection refused (os error 61)
    2: Connection refused (os error 61)
```

1. データベース名が見つからない場合のDNS解決エラー：

```
WARNING: This command is UNSTABLE and subject to breaking changes.
Error: the dns resolution of `quickstart-chat` failed.
```

このエラーが発生した場合は、正確なデータベース名を指定しているか確認してください。また、そのデータベースが正常に公開されていることも確認してください。

リデューサーの呼び出しに成功したら、ログを確認しましょう：

```bash
spacetime logs quickstart-chat
```

出力例：

```
2025-03-06T08:00:36.020453Z  INFO: spacetimedb: Creating table `message`
2025-03-06T08:00:36.020650Z  INFO: spacetimedb: Creating table `user`
2025-03-06T08:00:36.021162Z  INFO: spacetimedb: Database initialized
2025-03-06T08:04:31.514317Z  INFO: src/lib.rs:46: Hello, World!
```

ログから、データベースの初期化が正常に行われ、`send_message`リデューサーが「Hello, World!」メッセージを処理したことが確認できます。

### SQLクエリの実行

SpacetimeDBは、データベースに対してSQLクエリを実行するための便利な機能を提供しています：

```bash
spacetime sql quickstart-chat "SELECT * FROM message"
```

出力例：

```
 sender                                                             | sent             | text
--------------------------------------------------------------------+------------------+-----------------
 0x93dda09db9a56d8fa6c024d843e805d8262191db3b4ba84c5efcd1ad451fed4e | 1727858455560802 | "Hello, World!"
```

## クライアント側の実装

SpacetimeDBは複数のクライアント言語をサポートしています：

- [Rust](https://spacetimedb.com/docs/sdks/rust/quickstart)
- [C#（スタンドアロン）](https://spacetimedb.com/docs/sdks/c-sharp/quickstart)
- [C#（Unity向け）](https://spacetimedb.com/docs/unity/part-1)
- [TypeScript](https://spacetimedb.com/docs/sdks/typescript/quickstart)

クライアント側は、以下のような流れで実装します：

1. SpacetimeDBクライアントライブラリのインポート
2. データベースへの接続
3. リデューサーの呼び出し
4. データベースの変更の購読

特に、UnityゲームエンジンでSpacetimeDBを使用する予定があれば、[Unity総合チュートリアル](https://spacetimedb.com/docs/unity/part-1)が用意されています。このチュートリアルでは、基本的なマルチプレイヤーゲームの実装方法が詳しく解説されています。

詳細は各言語のクイックスタートガイドを参照してください。

## ユースケース

SpacetimeDBは以下のようなユースケースに適しています：

- **マルチプレイヤーゲーム**: 低レイテンシでリアルタイムな同期が必要なゲーム
- **チャットアプリケーション**: リアルタイムメッセージング
- **コラボレーションツール**: 複数ユーザーが同時に編集するドキュメントやツール
- **IoTアプリケーション**: センサーデータのリアルタイム処理と表示

実際に、MMORPGの「[BitCraft Online](https://bitcraftonline.com/)」のバックエンドは完全にSpacetimeDBモジュールとして実装されています。

## Maincloudサービス

SpacetimeDBは最近、「Maincloud」というサービスを提供開始しました。これは、自分でホスティングすることなく、SpacetimeDBを利用できるクラウドサービスです。特にゲーム開発者向けに設計されており、ACID（原子性、一貫性、独立性、耐久性）トランザクションをサポートするサーバーレスデータベースとして提供されています。

現在、Maincloudの「エネルギー」（サービスの使用に必要なリソース単位）が限定的に**90%オフ**で提供されているため、試してみたい方は絶好の機会かもしれません。

## まとめ

SpacetimeDBは、データベースとサーバーの機能を統合することで、リアルタイム通信アプリケーションの開発を劇的に簡素化する革新的なプラットフォームです。バージョン1.0にリリースされ、成熟したツールとして利用可能です。特に：

- データベース内でアプリケーションロジックを直接実行できる
- 複数のクライアント言語をサポートしている
- 状態を自動的にクライアントにミラーリングする
- 低レイテンシでリアルタイムな通信を実現する
- 新たに「Maincloud」サービスにより、サーバーレスな環境での利用が容易に

これらの特徴により、マルチプレイヤーゲームやリアルタイムコラボレーションツールなど、従来は複雑だったアプリケーションの開発が容易になります。

SpacetimeDBは今もアクティブに開発が続けられており、将来的にはさらに多くの言語サポートや機能が追加される予定です。

## 参考リンク

- [SpacetimeDB公式サイト](https://spacetimedb.com/)
- [SpacetimeDB GitHub](https://github.com/ClockworkLabs/SpacetimeDB)
- [SpacetimeDBドキュメント](https://spacetimedb.com/docs)
- [Maincloudサービス](https://spacetimedb.com/maincloud)
- [インストール方法](https://spacetimedb.com/install)