👾

リッチクライアントWEBアプリに挑んだ話

6 min read 2

この記事はバックエンドのサーバー構築やらデータベース構築やらを主にやってきたエンジニアがブラウザでグリグリ動くWebアプリを作って公開するまでの話です。ちょっとした教訓じみたことも書きますが、ただの独白ですので基本的には何の役にも立ちません。

作ったWebアプリ

https://vt.btj0.com/

図1: メインとなる画面のスクリーンショット
図1: メインとなる画面のスクリーンショット

作ったWebアプリは暗号資産(仮想通貨)のローソク足チャートを表示して仮想トレードできるサービスです。

ローソク足はアイコンをクリックかホットキーで進めたり戻したりコマ送りできます。水平線や十字線をチャート上に描画したりトレード結果はユーザー端末に自動保存されます。
ホスティングでちょっとサーバーを利用していますが、ほぼクライアント側で動くWEBアプリです。

以降で使う画面名を画面遷移図で提示しておきます。
図2: 画面遷移図
図2: 画面遷移図

主に利用した言語やライブラリなど

名前 バージョン 用途
TypeScript 4.3.2 型付きJavaScript
React 17.0.2 UI構築用JavaScriptライブラリ
Recoil 0.3.1 React用グローバル・ステート管理
Fabric.js 4.5.0 Canvas用ライブラリ
Next.js 10.2.3 React用フレームワーク
Vercel -- ホスティング

開発に至る経緯

当初はFX用の仮想トレードサービス構築を考えていました。というのも筆者は普段FXトレード用のツールなども開発しているため、FXの世界については多少知識があったためです。

FXでは仮想トレードできるツールとして有料でPCにインストールするタイプのものはありますが、Webサービスとしてはお目にかかったことがありませんでした。そして今ではトレード戦略をきっちり検証して本番トレードに臨む方が大勢いらっしゃるので、手軽にWebアプリで検証できるサービスであれば需要はあるかと思い開発に着手しました。

計画(約2日)

以下のようなサービスを目指すことにしました。

  • Apple製品のようにマニュアルが簡素でも何となく使える。
  • データ・プロバイダへの負荷軽減と同時にパフォーマンスを考慮する。
  • 必要最小限の機能(MVP: Minimum Viable Product)でリリースする。
  • なるべくお金をかけずに作る。

サービスの中核機能から徐々に作り上げていくことにしました。ざっくりと以下のような流れです。

graph TB
    A[テストビューのワイヤーフレーム作成]-->
    B[コア機能であるチャート描画機能の設計]-->
    C[チャート描画機能の開発]-->
    D[コア機能以外の開発とテスト]-->
    E[デプロイ]
    C-->B

コア機能の設計と開発(約1ヶ月)

テストビューのワイヤーフレーム作成

本サービスのメイン画面となるテストビューの画面イメージはFigmaを使って固めました。画面のワイヤーフレームを作っていると最低限必要となる機能の整理にもつながりました。

https://www.figma.com/

図3: Figmaのスクリーンショット
図3: Figmaのスクリーンショット

コア機能の設計

何はともあれローソク足データを取ってきて、それをチャートに描画してからローソク足の表示を制御できるようにしないと話になりません。ここでまず悩んだのが、ローソク足を保存するデータ構造でした。ローソク足データは新しい足順に配列に持たせておくのは定石ですが、それをチャートの描画とどのように連動すればよいのか紙とペンを使って思案しました。

検討の結果が以下図4のような流れでローソク足データを管理する方法です。

少し説明すると、データ・プロバイダからローソク足データを取ってきたら、ローソク足1本1本にチャートの初期X座標を追加してcandlesWithCenterXという変数名で保存します。そのcandlesWithCenterXのうちチャート上で表示するローソク足データだけをフィルターした配列をvisibleCandlesWithCenterXという変数名で保存します(Recoilのselectorを使えばcandlesWithCenterXに更新があれば自動でvisibleCandlesWithCenterXも更新可能)。こうしておけばReactのTSX(JSXのTypeScript版のこと)ではvisibleCandlesWithCenterXを単に表示すればよいので描画する責務だけに専念できるコンポーネントを作成できます。

図4: ローソク足データのfetchから変数に格納する流れ
図4: ローソク足データのfetchから変数に格納する流れ

ローソク足のデータ構造がうまく作れるとローソク足を進めたり戻したりスクロールしたりする機能は簡単に作れました。チャートの表示期間が移動してデータが足りなくなったらAPIを新しく叩いて、新しく取得した配列にX座標を追加して既存の配列にくっつけるだけで済みます。あとはよしなに見える範囲のローソク足が描画される仕組みです。

データ構造がビシッと決まると一気通貫に色々うまくいく。

コア機能の開発

メインのテストビュー画面において必要となる通貨ペアとテスト開始日の入力フィールドがあるテストページは簡素なもので済ませて、さっさとテストビュー画面の作成にとりかかりました。

ローソク足データの取得機能は慣れていたRESTだったこともあり簡単に作れましたが、描画に使ったCanvas(HTMLとJavaScriptでグラフィックオブジェクトを扱えるAPI)は初めて使ったのでMDN Web Docsを参照しながら見様みまねで作りました。

https://developer.mozilla.org/ja/docs/Web/API/Canvas_API

ローソク足チャートが表示されて想定通り動かせたときは、これでいけると思い歓喜でした。が、のちにCanvasだけでは難しい局面にぶちあたりCanvasが手軽に使えるようになるライブラリを使うことになります。

あとローソク足データの配列はグローバル・ステートとして管理しましたが、これにはRecoilを使いました。グローバル・ステート管理の定番であるReduxはToolkitを使ってもまだ冗長な書き方になってしまいあまり好みではないのです。RecoilはFacebook謹製でまだ実験的なステート管理ライブラリですが、非常にシンプルに書けました。ただReduxはDevToolsやpersistなどの完成されたライブラリなどが揃ってはいます。

https://recoiljs.org/

コア機能以外の開発/テスト(約2ヶ月)

2番目に大事な機能の開発

コア機能ができたので次に大事なチャート上に図形を描画する機能に取り掛かりました。この機能はトレード履歴(エントリー位置や決済位置)の描画や十字線や水平線の描画に使う必須機能です。

ここで問題が発生しました。Canvasで描いた図形は個別にIDが割り振られているわけではないので、特定の図形を選択して削除したりプロパティを変更したりできないのです。たった1個の図形の変更でもチャート全体の描画を更新しないといけませんでした。Canvasだけでも頑張れば図形を個別に更新できるのかもしれませんが、そんな時間はありませんでしたので、適当なライブラリを探しました。Fabric.jsというCanvasを楽に操作できるようにしたものを見つけこれを使いました。

http://fabricjs.com/

サービス内容の大方針転換

ここまで来たらあとはトレード機能の作成とプロジェクトの保存機能の作成で、なんとなく頭の中では実装の見通しが立っていたので後は楽勝と思っていました。

そんな時、思いも寄らないメールがデータ・プロバイダから届きました。その内容はそれまでデータ取得は無料だったのが有料になるというものでした。すぐに料金を問い合わせたところ、年間2万ドルという回答でした。とても払える金額ではないので他のFXデータ・プロバイダを探しましたが、アクセス制限が厳しかったりと中々条件がよいプロバイダが見つかりませんでした。

ここまで来て開発中止かと頭をよぎりましたが、とつぜん天啓を得ました。

「暗号資産でもいいじゃない」と。

暗号資産もFXもローソク足チャートを使って取引するところは同じですし、暗号資産の取引所はデモ口座を提供していないところがほとんどなので仮想トレード・サービスはFXより有用かもと思いました。ただ暗号資産は値動きが不安定で投機的側面が強いので検証しようと思う人がいるのかは疑問でしたが思い切って方針転換しました。この柔軟性は個人開発のいいところです。そしてリリース前の変更でラッキーでした😁

コア機能の作り直し

新しいローソク足データ・プロバイダもRESTだったのですが、当然リクエスト形式もレスポンス形式も異なります。ローソク足データは詰まる所、始値/高値/安値/終値/開始時刻の配列からなり変わりはないので、データをフェッチする関数を隠蔽するような作りにしておけば、個別のフェッチ関数だけを新たに作ればすぐに切り替えられるはずです。しかし筆者はこのことを怠り、関数を分けることもなく密に作っていました。結局このことが仇になり大がかりな工事になってしまいました💦

データフェッチ先が変わることだってある。万一変わっても大丈夫な汎用性はあるか。

残りの機能の開発

ここまでで開発が残っている主要機能は、トレード機能(注文や決済や統計データ表示など)とトレード履歴の保存機能です。前者はRecoilでデータ管理してコア機能を使ってチャートにエントリー位置などを描画させるだけなので難なくできました。

意外と作り込まなくてはいけなかったのが、ユーザー端末へのトレード履歴保存機能でした。この保存機能ですが要はRecoilで管理しているステートをプロジェクト単位にlocalStorageに保存する機能なのですが、サードパーティとして既にあったRecoil用persistライブラリは階層構造になっているステートを保存できませんでした。仕方なく自前で作りましたが、RecoilのatomFamilyの扱いに苦戦し予想外にてこずりました。

あとは下記作業もやりましたが長くなりすぎるので割愛します。

  • トップページの作成
  • お知らせページの作成
  • 国際化(i18n)対応
  • デザインの調整
  • テスト

デプロイ(約1日)

Next.jsを開発している組織が運営しているVercelというホスティング環境を使えばデプロイはあっという間でした。GitHubアカウントでVercelのサイトにログインしてボタンをポチポチすればリポジトリを勝手に読み込んでデプロイしてくれます。独自ドメインもドメイン・プロバイダでCNAMEを登録してVercelにドメインを入力するだけです。しかもHTTPS化もデフォルトです。これで全て無料なんて信じられません。

ただし筆者の場合はデプロイでエラーになってしまいました。Fabric.jsで使っていたCanvasモジュールをダウングレードしてとりあえず回避しました。これにて晴れて全世界にサービスを公開できました。

https://vercel.com/solutions/nextjs?utm_source=next-site&utm_medium=banner&utm_campaign=next-website

Vercel凄すぎ

最後に

作ったサービスも使ってもらわなければ意味がないですが、今のところ閑古鳥も筆者もないています。再訪もないことから、やはり暗号資産の仮想トレードは需要がなかったのかもしれません。

以下のようなサービスを目指しましたが、結果は太字のとおりです。

  • Apple製品のようにマニュアルが簡素でも何となく使える。
    😑 ツールチップやマウスカーソルの形状変化だけでは全体的な使い方を伝えるのは難しい
  • データ・プロバイダへの負荷軽減と同時にパフォーマンスを考慮する。
    😁 必要になったときに必要なだけデータを取得する設計にしたことで不愉快になるほど動きが遅いということもなかった
  • 必要最小限の機能(MVP: Minimum Viable Product)でリリースする。
    😑 何を必要最小限とするかは難しいところ
  • お金をかけずに作る。
    😁 コストは筆者の労力だけ

今回リッチクライアントなWEBアプリを作ってみて、アプリ内のデータ構造とその管理方法(ステート管理)はやはり大事だと感じました。これはサーバーサイドのプログラミングと一緒でした。内部のデータ(ステート)がきれいに繋がると無駄なコードがなくうまいこと動いてくれました。