リリースするかは未定だけど
以前から将棋関連アプリのオンライン対戦機能を作ってみようかと、FirebaseのCloud FunctionsをJavaScriptで使ってみたりしていたのですが、従量制なので実際運用し始めたらどれぐらいの費用が掛かるのか分からないので、なかなか本気になれませんでした。開発することを考えるとオンライン対戦機能をどのように実現するかという問題の方が先ですが、運用コストを考えるといろいろ迷います。そこで定額制で運用することを考えると自分の場合はRailsかNode.jsかな〜と思い、取り敢えずNode.jsで着手してみました。
結局Herokuで試すことに
ローカル環境である程度作ったところでリモートサーバーで試してみたいと思って、定額制でNode.jsの運用先を探したところPaiza Cloudというのを見つけたのですが、ここは無料プランやライトプランではサービスを公開出来ないようです。
以前の記事でNode.jsを使ったアプリを、MongoDBのアドオンが使えなくなったために仕方なく非公開にしたことを書きましたが、そんなことがあったのでもうHerokuでMongoDBを使うのは止めようと思っていたのですが、無料でリモート環境で試せるHerokuはやはり魅力的です。実際にリリースするかどうかも決めてないので尚更です。
MongoDB Atlasを使う
HerokuでMongoDBのアドオン(mLabアドオン)が使えなくなったので、代替方法であるMongoDB Atlasを使うと料金が発生するかもと心配しましたが、Atlasの方は新たなアカウントを作らずにgoogleアカウントでログインするだけでデータベース一つだけなら無料で使えるらしいです。こちらの記事の通りにすれば、簡単に環境が作れました。
Heroku(本番で使うかどうかは未定)で動作確認
ブラウザを2つ立ち上げて2台のスマホ端末に見立てて動作確認してみました。
ちゃんと動いて一安心です。
全体の3割ぐらい完成か?
自分の将棋関連アプリの中から5五将棋アプリを選んでここまで作りましたが、今になって5五将棋は既にSDINサイトでオンライン対戦が可能なので、他のアプリ(例えば禽将棋アプリ)にした方がよかったかも?なんて考えてます。まぁ他のアプリでも使えるように考えながらサーバーサイドを作っているつもりですが、最終的にどうするかは決めてません。
あと、5五将棋アプリは初期配置をユーザーが自由に編集可能なので、対戦申し込みする時に初期配置を相手に表示して「この初期配置だけど、いいですか?」という意志を確認するために対戦申し込みする側の初期配置を相手側に通知する機能の実装が面倒だったのですが、開発時の苦労話や技術的なTipsみたいな物も今後記事にするかも知れません。
MongoDB(NoSQL)とRDBとの違い
例えば棋譜テーブルは以下のように定義しています。
var kifuSchema = new Schema(
{
description: {
type: String
},
firstPlayer: {
type: String,
required: true
},
secondPlayer: {
type: String,
required: true
},
applicant: {
type: String,
required: true
},
koma: {
type: Array,
default: []
},
histories: {
type: Array,
default: []
},
users: [{ type: Schema.Types.ObjectId, ref: "User" }]
},
{
timestamps: true,
}
);
配列型のカラム(histories)を用意して、指し手が進む毎にスマホ端末から貰ったJSON形式の棋譜データをそのまま追加してます。このやり方なら禽将棋であろうが5五将棋であろうがテーブル構造を変える必要がありません。
以下のコードは対局が一手進んだ時のサーバー側の処理です。
client.on("gameMove", data => {
let filter = {_id: data.kifuId};
let update = {$push: {koma: data.koma, histories: data.record}};
Kifu.findOneAndUpdate(filter, update, {returnDocument: 'after'})
.then(kifu => {
return kifu;
})
.then(kifu => {
User.find({_id: {$in: [kifu.users[0], kifu.users[1]]}})
.then(users => {
if (users[0].socketId == client.id){
io.to(users[1].socketId).emit("gameMoved", kifu);
} else {
io.to(users[0].socketId).emit("gameMoved", kifu);
}
})
})
.catch(error => {
console.log(`Error in io.on.gameMove:${error.message}`);
});
});
RDB(リレーショナルデータベース)だと配列型のカラムではなく別テーブルにすると思いますが、そうすると将棋の種類が変わるとテーブル構造が変わりなかなか汎用的に作れません。NoSQLデータベース(MongoDB)ではテーブルの正規化とかあまり考えずに緩く定義(冗長化)して使えるのが利点だと思います。最近のRDBでは配列型のカラムを使用することは出来るかも知れませんが(私は使ったことがありません)、今のところNode.js+MongoDBで作ることにしてよかったなと思ってます。
この記事によると「NoSQLデータベースの設計では、正規化をしてはいけない」と言う人もいるそうです。それは極端かも知れませんが、RDBで配列型のカラムを使うぐらいならNoSQLデータベースを使う方がいいような気がします。