isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

MySQLからリアクティブにFirebaseを更新するものを作った

コンセプトは f(RDB)=> Firebase

モチベーション

Firebase Realtime Databaseを使う上でこのような問題点がある

  • あえて非正規化した場所が、バグによって正しく更新されず不整合になるリスク
  • 正確なアクセス権限設定が極めて難しい
  • 枯れたRDBほどマイグレーションツールが揃っていないためAlter Table的な操作をしたくない

そこで、Realtime Databaseの前にRDBを置き、RDBからReactiveにRealtime Databaseを更新すれば解決できると考えた。

実装

MySQLから更新イベントを受信するためにはmysqlbinlogというコマンドを使う。
これは本来全然別のことに使うためのものだが、せっかくなので使う。

RDBの全データを引数にRealtime Database上の全データを作る純粋関数を書く。

mysqlbinlogが標準出力に何かを書いたら、それに反応してfirebaseを更新する。

サンプル

void main() async {
var command = "mysqlbinlog --read-from-remote-server --short-form --host=localhost --user=your_name --password=*** --stop-never mysql-bin-changelog.000001";

var process = await Process.start(command , []);
process.stdout.listen(onStdout);

}


class UserEntity {
int userID;
String name;
}

class MessageEntity {
int messageID;
DateTime time;
String text;
int userID;
}

void onStdout(List<int> data) async {
print("-" * 10);

var conn = await MySqlConnection.connect();

var result = await conn.executeStreamed("select * from users");

List<UserEntity> users = [];

await for (var row in result) {
var user = new UserEntity()
..userID = row[0]
..name = row[1];
users.add(user);
}

var result2 = await conn.executeStreamed("select * from messages");

List<MessageEntity> messages = [];

await for (var row in result2) {

var message = new MessageEntity()
..messageID = row[0]
..time = row[1] as DateTime
..text = new Utf8Decoder().convert((row[2] as Blob).toBytes())
..userID = row[3];
messages.add(message);
}

messages.sort((m1, m2) => m1.time.compareTo(m2.time));


var fbMessages = messages.map((m) => <String, dynamic>{
"time": m.time.toLocal().toIso8601String(),
"text": m.text,
"userName": users.firstWhere((u) => u.userID == m.userID).name,
}).toList();


await fbClient.put("/messages",fbMessages);

}

今後の課題

今はパフォーマンスが悪すぎるので改善したい。

  1. バイナリログをきちんと読み取って、毎回全データを落としてくるのではなく、関係のある行のみを落としてくるようにしたい
  2. Realtime Databaseに全データを書き込んでいるので、今回の出力と前回の出力を比較して差分更新できるようにしたい
  3. 今はRealtime Databaseに書き込む全てのデータを毎回作っているので、関数を小分けにして、無関係な部分は実行しないようにしたい

また、クライアント側からのアクションをMySQLに書き込むところも上手く抽象化したい