暗号通貨のリアルタイム価格表示アプリの裏側

# はじめに
毎年参加しようと思ってもすぐ枠が埋まってしまっていたので、今回初参加のアドベントカレンダーです！😃

たくさんの暗号通貨（仮想通貨）の価格を一度に比較できるスマホアプリを作って公開したので、その実装がどうなっているのかについて紹介します。

# アプリの説明
[Cryptopippi（クリプトぴっぴ）](http://onelink.to/cryptopippi)

[![Android.png](https://qiita-image-store.s3.amazonaws.com/0/27863/4b805780-fce5-aa04-4a75-9e6e180ace04.png)](http://onelink.to/cryptopippi)

>Cryptopippi（クリプトぴっぴ）は仮想通貨のアービトラージ（裁定取引）に役立つリアルタイム価格比較アプリです。
>国内取引所はビットフライヤー、コインチェック、ザイフはもちろんGMOコインにも対応。国内外15以上の取引所におけるビットコイン/アルトコイン/トークンの価格差が一目でわかります。

特にヘルプや説明もなく、価格が一覧で表示＆更新されるアプリです。

なんだか作るのも簡単そうな気がしますね。

# 何で開発しているのか
Cryptopippiはなんと<b>Unity</b>で作られてます。Unityといえば3Dゲームを作るのに用いられるゲームエンジンですが、3Dでもなければゲームでもない<b>ツールアプリ</b>の開発にも使えます。

# なぜUnity？
自分が元ゲームエンジニアで、一番手に馴染んだもので手っ取り早く作りたかったのが大きな理由です。

その他Unityを使うことのメリットを上げてみます。Xamarin使える人ならXamarinでいいかも。

* マルチプラットフォームへのビルド対応（iOS,Android,Windows,Mac,Linux,Switch, etc..）
* 開発言語がC#
* 開発を加速させる有料/無料アセットがストアにたくさんある
* デフォルトでアプリにアナリティクス機能が搭載される（Unityの管理画面から確認可能）
  * カスタムイベントも送信＆分析可能
* Unity IAPでApp StoreとGoogle Playの両方の課金処理がいっぺんに作れる

ワンソースでスマホ、スタンドアロンなんでも書き出し可なのがすごく良いです。スマホ版しかリリースしてないですが、自分が使うときはMacのスタンドアロン版をビルドして使ってます。C#も非常に良い言語です。

Unityでのツールアプリ開発におけるデメリットは以下です。

* アプリの起動が遅い
* アプリの容量が大きくなりがち
  * アセットを外出ししてDLさせることは可能
* 用意されてるUIのコンポーネントが貧弱すぎる
* ネイティブのウィジェット機能の実装は無理

アプリの運用とアップデートを繰り返しているうちに上記が割りと大きな問題になってきています。

「起動しなくてもウィジェットで手軽に価格を見たい」という需要を満たせないのと、ロジックが実装できてもUIパーツを作るのに時間がかかるのが難点。

基本的にゲーム毎にUIが異なるせいなのか、HTMLみたいに手軽に使えるシンプルなデフォルトコンポーネントはあまり用意されてません。グラフ描画ライブラリとかがかなり豊富なJSが羨ましい。

# 内部的な処理はどうなっているか
アプリの基本的な動作は以下のようになっています。

0. 各取引所のティッカーAPIを叩く
0. レスポンスがJSONで返ってくる
0. JSONをデシリアライズする
0. 価格を表示（ビューに反映）
0. 取引所間価格差を計算して表示（ビューに反映）

簡単にいうと、すごく短い間隔で大量にポーリングしてます。通信量？なにそれ？

大体の取引所はWebSocketで値をPushする仕組みも提供してるのですが、たくさんの取引所に対応する都合上HTTP通信する実装が楽にできそうだったので、つい出来心で...。

真のリアルタイムへは今後対応・改善していく予定です。

# 苦労した点
なんだか簡単そうに見えるかもしれないですが、裏側ではいろいろ頑張ってます。

* 取引所やAPI毎に
  * インターフェースやレスポンス内容が異なる
  * sec,min,hあたりのアクセス回数制限があったりなかったり
* エラー時のリトライ考慮
  * 例えばZ◯ifのAPIは頻繁にエラーを返す
  * エラーが返ってきたらある程度時間をおく必要があったりなかったり

実際にAPIを叩くレイヤーより上の抽象化されたレイヤーでは`ITicker`インターフェースを作って各取引所のティッカー情報（Last Price以外にもVolumeやBest Ask,Best Bidなど）を同じように扱えるようにしてます。

が、中にはAPIを2つ叩いて内容を合算しないと`ITicker`を満たせない取引所があって待ち合わせたり、さらにそれにコール回数制限とエラー時リトライが重なってきて...

やっと難しそうに感じてもらえましたでしょうか？

# 利用している技術・ライブラリ

|ライブラリ名|説明                |
|:--------|:------------------|
|<b>UniRx</b>|Unity用に拡張された<b>Reactive Extensions（Rx）</b>のライブラリ|
|Zenject|Unityで使えるDIライブラリ|

特に大活躍してくれているUniRxについて簡単に紹介します。他にもUnity向けのライブラリをいくつも入れてますが割愛。

# Rxでできること
Rxはイベントを超拡張したようなObserverパターンの実装の一つです。

時間が関係する処理、例えばAPIがエラーを返したときのリトライや通信のループ（ポーリング）を行ったり、指定した非同期処理を待ち合わせて全部完了したら処理を行うなどが非常に完結に記述できます。

使いこなせばもうこれなしでは何も実装したくないと思えるくらい便利です。

コード例）

```csharp
// APIを通じて最終取引価格を取得
public IObservable<string> GetLastPriceAsync(){ return /* よしなに実装 */; }

// APIを通じて現在の最安売値・最高買値を取得
public IObservable<string> GetBestPricesAsync(){ return /* よしなに実装 */; }

// 待ち合わせと値の合成
Observable.CombineLatest(
     GetLastPriceAsync(),   // 最終取引価格を取得
     GetBestPricesAsync(),  // 現在の最安売値・最高買値を取得
     Tuple.Create           // 2つのレスポンスをまとめる
  )
  // どちらかのAPIでエラーが起きたら3秒後にリトライ
  .OnErrorRetry<string, Exception>(e => { /* ログ出力 */ }, TimeSpan.FromSeconds(3))
  .Select(t => new Ticker(t.Item1, t.Item2))  // 2つのレスポンスからTickerのインスタンス生成
  .Do(ticker => price = ticker.lastPrice)     // ビューを更新
  .Delay(TimeSpan.FromSeconds(0.1f))          // 待ちを入れる
  .Repeat()                                   // 繰り返す
  .Subscribe();
```

たった10行ちょっとで必要な処理がほぼ全て書けています。すごすぎる！

実際の処理はもうちょっと複雑ですが、リトライや射影、ループなど一つ一つのやりたいことがそれぞれ一行で書けるRxの強力さが伝わったでしょうか？

# その他
[Cryptopippi](http://onelink.to/cryptopippi) は各取引所のサーバ以外（ほぼ）サーバレスのアプリです。

唯一使っているバックエンドが [NCMB（ニフクラ mobile backend）](http://mb.cloud.nifty.com/) で、起動時にNCMBからメンテナンスフラグを取得して起動できない状態にしたりしています。

現在はAPIキーを使った実際の取引を行う機能の実装を進めていて、来年のどこかでアプリ内機能として公開するかもしれません。

https://twitter.com/su10_dev/status/940884278614769664

# 最後に
みなさまよい暗号通貨ライフを！

[Cryptopippi（クリプトぴっぴ）](http://onelink.to/cryptopippi)
[![Android.png](https://qiita-image-store.s3.amazonaws.com/0/27863/4b805780-fce5-aa04-4a75-9e6e180ace04.png)](http://onelink.to/cryptopippi)