BaaS (BallPen-as-a-Service)
BaaS (BallPen-as-a-Service) は,技術も運用も冗長にしたボールペンカチカチサービスです.世界中でボールペンがカチカチされた回数が共有されます.
ボールペンをカチカチしてほしい,ボールペンをカチカチさせてほしい,世界中のそんな願いを叶えます.
状態やカウントを共有しているせいで,同時に複数人が触ってるとボールペンが正しい動きをしてくれないのは 許してください 仕様です.みんなのサービスなので.
こちらから遊べます.
おまけ
以下のリポジトリでソースを公開しています.
技術
- Cloudflare Workers
- Cloudflare Durable Objects
- Hono
- React + Vite
- TypeScript
アーキテクチャ
モノリス構成でCloudflare Workersにデプロイしています.カチカチカウントのデータストア(状態管理・永続化)にはCloudflare Durable Objectsを使っています.最初はDurable Objectsの代わりにKVを使っていたのですが,レイテンシや整合性の観点から変更しました.
バックエンド
Honoで API を書いています.API は,Durable Objectsインスタンスへの参照・更新処理と,フロントエンドのアセット配信をしています.
Durable Objectsで,ペン先が出ているかどうか,現在の状態(画面が押されているか),カチカチカウントを保存しています.
フロントエンド
操作ロジックとしては,handleDownとhandleUpで,画面を押した瞬間と話した瞬間,API を叩くようにしています.特に,handleUpでは UX 向上を意識して,画面更新を瞬時に行う楽観的更新をするようにしています.
以下がhandleUpです.楽観的更新はフロントエンドで直接状態更新をしており,これによりラグを感じることなく操作することができます.その後,API を叩いてサーバとの整合性を保ちます.また,processingRef.currentで Promise チェーンによる処理の直列化をして,連打されても整合性を保つようにしています.
const handleUp = () => {
setIsPressedDown(false);
new Audio("/release.mp3").play().catch(() => {});
// 楽観的更新
setPenTipOut((prev) => !prev);
setPressCount((prev) => prev + 1);
processingRef.current = processingRef.current.then(async () => {
// 実際のリクエスト
const res = await fetch("/release", { method: "POST" });
const data = (await res.json()) as PenState;
// レスポンスを確認して整合性を保つ
setPenTipOut(data.penTipOut);
setPressCount(data.pressCount);
});
};
状態に応じて,ペン先が収納された状態と,ノックを押し込んでいる状態,ペン先が出ている状態の3つの画像を切り替えています.
CI/CD
最初,Cloudflare Workers へのデプロイは,Wranglerという CLI ツールを利用していました.これにより,npm run deployでデプロイが完了するため,お試し段階では重宝しました.このプロジェクトで初めてWranglerやCloudflare Workersを利用したのですが,思ったより簡単にデプロイまで操作出来て感動しました.
その後,GitHub Actionsを書き,そちらでデプロイまでするように変更しました.
おわりに
今回は,いろんな技術を学ぶためのプロジェクトでしたが,チュートリアルとしては楽しくある程度深く学べたと思います.特に,Cloudflare Workersのデータストアについて詰まりながら学べたのがよかったです.
Ballpen-as-a-Service を言いたいがために作った一面もありますが,モダンな技術でリアルタイム性のあるものを作って遊べたのが,今後の技術選定やアイデアの幅にかなり影響を与えられると思います.
お付き合いいただきありがとうございました.
