REST RPG Battle
最近 REST な API 設計を全然していません.
なぜかといえばゲームの操作って総じて RPC 的であると思っているからなのですが, 本当にそうなのでしょうか?
よく考えてみると REST でゲームの API 設計をしたことがないので, 一般的なターン制 RPG のバトルを設計したらどうなるかをやってみたいと思います.
RESTful API
正しい定義については https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm とかを読んでもらうとして
設計の前に自分の雑な REST のポイントは以下の4点になります.
- ステートレスである
- 全てのリソースは一意な URI を持つ
- 統一のインターフェイスで操作できる
- リソースは他のリソースへのリンクを持つ
これらを意識しながら API の設計をしていきたいと思います.
バトル仕様
まずはバトルの仕様ですね.
とりあえず
- プレイヤー2名の 1v1 の対戦
- ターン制でプレイヤーは交互にアクションを行う
- 各プレイヤー1ターンで1回のアクションを行うことができる
- アクションでは以下のいずれかができる
- 攻撃
- 相手の HP を 10 減らす
- 魔法攻撃
- 相手の HP を 30 減らす
- 防御
- 次のターン, 相手の魔法攻撃で HP が減らなくなる
- プレイヤーは HP 100 を開始時に持つ.
- アクションの結果, HP が 0 になったプレイヤーの敗北
みたいな単純なバトルを考えてみます.
設計
- 通信には HTTPS を使うこととします.
- BODY のフォーマットには JSON を利用するので
Content-type: application/ json
を指定します. - 認証のことは忘れます.
バトル作成
REST ですからリソースを作らなければあらゆる操作ができません.
バトル作成は POST /battles
で 201 Created
で返ってきて, 作成されたリソースの URI が Location: /battles/2
みたいにヘッダー返ってくる感じで良さそうです.
結果の BODYや GET /battles/2
で取れる内容は
{
"player": {"hp": 100},
}
ってところですか. とりあえず2つAPIを作成しました.
URI | Method | 用途 |
---|---|---|
/battles |
POST | バトル作成 |
/battles/{battle_id}/ |
GET | バトル情報取得 |
プレイヤー情報
よく考えると, player はバトルとは別のリソースな気がしますし, このままでは対戦相手のHPが見えません.
となると別のURIに切りださなければなりませんが, 別のリソースはリンクで示すことになっているので GET /battles/2
の結果は
{
"links": [
{
"rel": "players",
"href": "/battles/2/players"
}
]
}
みたいにして, GET /battles/2/players
でプレイヤー一覧を取れるようにする必要がありそうです.
バトル作成でプレイヤーを作成するのは, URI で指定したリソース以外を触っていてよろしくありません.
POST /battles/2/players
でバトルへの参加 = プレイヤーの作成にします. バトルと同じように Location: /battles/2/players/21
みたいにリソースの URI を返します.
となると以下のAPIを追加です.
URI | Method | 用途 |
---|---|---|
/battles/{battle_id}/players |
GET | プレイヤー一覧取得 |
/battles/{battle_id}/players |
POST | プレイヤー作成 (= バトル参加) |
/battles/{battle_id}/players/{player_id} |
GET | プレイヤー情報取得 |
GET /battles/2/players
は
[{
"id": 21,
"hp": 100
},{
"id": 31,
"hp": 100
}]
GET /battles/2/players/21
は
{
"hp": 100
}
でとりあえず大丈夫そうです.
REST っぽくなってきた.
ターン
バトルが作れて, プレイヤーも作れたので早速攻撃したいと思います.
ATTACK /battles/2
とかできればいいんですが ATTACK
というHTTPメソッドは無いのでできません.
どうすれば良いでしょうか.
攻撃がなんらかのリソースの作成だと考えると, 攻撃した結果できるのはそのターンが作成されると思い至ったので POST /battles/2/turns
で Location: /battles/2/turns/1
が返ってくるとしてアクションとすれば良さそうです.
{
"player_id": 21,
"action": "attack"
}
player_id
は認証から取るのとどっちがいいんでしょう?
認証から取るのはステートレスで無い気がするので, きちんと POST に含めた上で, 認証している ID と違っていたら 403
返すとかでしょうか. アクションする順番じゃ無いとかも 403
?
URI | Method | 用途 |
---|---|---|
/battles/{battle_id}/turns |
GET | ターン一覧取得 |
/battles/{battle_id}/turns |
POST | ターン作成 (= アクションを行う) |
/battles/{battle_id}/turns/{turn_id} |
GET | プレイヤー情報取得 |
GET /battles/2/turns/1
は POST で渡した BODY をそのまま返せば良さそうです.
{
"player_id": 21,
"action": "attack"
}
そのターンの攻撃した結果のプレイヤーの HP とか入れたくなるのですが, それは別リソースですからクライアントが PUT /battles/2/players/21
して HP を減らすことにします.
URI | Method | 用途 |
---|---|---|
/battles/{battle_id}/players/{player_id} |
PUT | プレイヤーHP更新 |
PUT /battles/2/players/31
で相手のHPを 10 減らします.
{
"hp": 90
}
これで攻撃して相手の HP を減らすことができました!
あとは繰り返していけば良さそうです.
まとめ
RPC風な API だったら /battle/create
/battle/action
/battle/info
の3つも用意すれば完了でしたが, REST 風にすると以下の 9 API を作成することになりました.
URI | Method | 用途 |
---|---|---|
/battles |
POST | バトル作成 |
/battles/{battle_id}/ |
GET | バトル情報取得 |
/battles/{battle_id}/players |
GET | プレイヤー一覧取得 |
/battles/{battle_id}/players |
POST | プレイヤー作成 (= バトル参加) |
/battles/{battle_id}/players/{player_id} |
GET | プレイヤー情報取得 |
/battles/{battle_id}/players/{player_id} |
PUT | プレイヤーHP更新 |
/battles/{battle_id}/turns |
GET | ターン一覧取得 |
/battles/{battle_id}/turns |
POST | ターン作成 (= アクションを行う) |
/battles/{battle_id}/turns/{turn_id} |
GET | プレイヤー情報取得 |
今回はバトルの仕様もかなり単純なものにしているのでカードやデッキを使ったり, 場の効果やスキルといった概念が入ってきたりするとさらにたくさんのリソースを定義する必要がありそうです.
また, 自分のリソースの操作するときに, 他のリソースの操作をしないようにするため, クライアント側に複数のリソースの API を叩いてもらう必要があったりとかもポイントでしょうか.
なかなか楽しかったです.