Balto サービス終了まで目前となりました。
ここまで来る間に様々な技術と出会い、格闘し、今の Balto があります。
今日は Balto の開発の歴史について振り返って行きます。

ちなみに私が iOS エンジニア兼サーバサイドエンジニアなので話の中心はこの2つになります。

Baltoとは

Balto 年表

御存知の通り、開発中のアプリを配布してフィードバックを送る、というシンプルなものです。
その起源は社内メンバーが開発中のアプリに対するフィードバックをスプレッドシートで管理するのが大変、かつスクリーンショットを撮って共有するのに手間がかかる、という問題からでした。

Balto によって1回のフィードバックにかかる時間を数分から約30秒に短縮。結果社内のプロジェクトの殆どで利用されるようになりました。

こういったシンプルな苦痛がサービスに繋がるのが面白いところですね。

くわしくはこちらの記事を。リリース後は、TechCrunchさんにも取り上げられました。
コアとなる機能はシンプルですが、裏側では色々なことをやっています。

技術選定

Balto の技術的な構成はざっくり以下になります。

サーバサイド

  • 言語:Elixir
  • フレームワーク:Phoenix
  • RDB:PostgreSQL (途中まで MySQL)
  • サーバ:Heroku・AWS

フロントエンド

  • ベータ版まで Vue.js、正式版は React/Redux

iOS

  • Swift

Android

  • JAVA

技術選定の流れ

サーバサイドの選定にときに要件そのものは軽かったので技術的な挑戦も含め、当時騒がれていた Go か Elixir を使用しようかと思っていました。
そして Goodpatch では Ruby を書けるエンジニアが多く在籍しているので、Ruby ライクなシンタックスを持つ Elixir を選びました

フロントに関しては、当初エンジニアは一人だけだったのでリリース優先のため使い慣れた Vue.js を選択。

iOS は当時すでに主流になってきた Swift での開発を選びました。

ベータ版まで(2015/12 〜 2016/3)

ベータ時代のWebダッシュボード

ベータまでに必要だった機能は

  • アプリを配布する
  • アプリのスクリーンショットを撮りフィードバックを送る

というシンプルなものです。

配布機能

iOS アプリの配布機能自体は OTA (Over-The-Air) の仕組みを利用して行えば良かったのですが、 OTA で配布するための Plist を作成しなければなりません。
更にこの Plist には iOS アプリの Bundle Indentifire の値を入れなければならず、アップロードされてきた IPA ファイルの中身を解析する必要があります。

当初、IPA の中の Info.plist (XML) を見れば良いと思っていたので軽く見ていたのですが、見事にこの Info.plist がバイナリ化されていました

泣く泣く Elixir という慣れていない言語でバイナリの解析を作る羽目になります。

さらにアプリのアイコンも PNG だと思っていたのですが、 IPA に入るときに CgBI PNG という通常の PNG ではないものになっていました
配布したアプリの管理画面でアプリのアイコンを表示させることは決まっていたので、この CgBI PNG を通常の PNG に戻すべく、再びバイナリとの格闘が……。

この時作ったものはライブラリ化して公開してあります。

bplist
cgbi-to-png

このバイナリ解析の実装を終え感じたのは、Elixir でバイナリを弄るのは意外と楽ということです。
パターンマッチや再帰処理を利用して、他の言語と比べ短く書けたような気がします。

フィードバックを送る

この機能を配布したアプリにつけるにはライブラリか SDK で予め機能を入れた状態で配布してもらう必要がありました。
Balto ではセキュリティ面などを考慮して、中のコードが分からないように SDK 形式で作ることにしました。

SDK を作る情報はあまり世の中に出回っておらず、手探りの状態で作り始めました。
組み込んだアプリを邪魔しないように作る必要があり、かなり神経を使います。

このあたりの苦労は去年のアドベントカレンダーにあります。

Balto開発で得たiOSのSDK開発の知見

このような苦労を経て、 Balto のベータ版(当時は Updraft という名前)が世に出ました。

ベータリリース直後

ベータ時代のiOS

MySQL 遅かった問題

現在では PostgreSQL ですが、当初は使い慣れた MySQL を利用していました。
しかし Heroku のアドオンである ClearDB と JawsDB、 AWS の RDS、 Heroku の PostgreSQL で速度比較を行ったところ、 PostgreSQL が一番早い結果になりました。

この移行のときに SQL の方言の問題やカラムの型、AutoIncrement の違いについて悩まされました。

名前変更

Updraft という名前でリリースしたのですが、すでに海外に同じ名前で似たサービスが存在していたので、急遽変更することになりました。

コードのいたるところに Updraft の名前があり、直すのが大変でした。
サービスの名前を決めるときは慎重に……!

正式版リリースまで(2016/7〜2017/1)

サービス名の変更、ユーザインタビューなどを終え、ほぼほぼゼロから作り直す形でスタートしました。
この時エンジニアが二人増え、三人での開発体制になります。

動画フィードバック機能

Android は OS にあるキャプチャ機能を使って作れるのですが、 iOS はそんなに単純にいきませんでした。

取った方法は非常に原始的。
動画の元になるスクリーンショットを大量に撮って、AVFoundation を使って動画に仕上げる、ということでした。

意外に簡単そうに思えたのですが、動画を6秒に揃えたり、CPU やメモリ使用量などに注意する必要がありました。

スクリーンショットにシェイプを描く

単純な四角や丸を描くのは簡単です。
しかし矢印が非常に厄介です。

矢印の先端の三角部分を描こうとすると単純な計算では求まらず、久々に数式と格闘。

この機能に関して、その求め方が Android と iOS で違うのが面白いですね。
答えの求め方が一つにならないという数学の良さが現れている気がします。

iOSでの矢印の座標の求め方

正式版リリース後(2017/1〜現在)

無事に正式版をリリースできた直後、問題が起こります。

In-App Purchase を実装しないとリジェクト問題

ベータを終え、有料になった直後に iOS アプリが課金導線を持っていないことからリジェクトを食らうようになりました

課金部分はお金が絡むので神経質になる部分です。少しの実装ミスが重大な問題を生むことがあります。

なので今までスピード重視の実装で汚くなっていたコードを Clean Architecture を使って書き直しました。
Clean Architecture はコード量は増えるものの、コードを役割ごとにレイヤーにわけて独立性を高くするので、テスタビリティが高くなります。今後の長期的な運用を踏まえての採用でした。

なるべく早く、全てのコードを書き直したので100% Clean Architecture に厳密に従ったわけではなかったのですが、ビジネスロジックの部分はテストも書くことができ、満足行く結果になったと思います。

In-App Purchase は Balto の課金体系に併せて辻褄を合わせるのに苦労しました。
そしてあらゆるパターンを想定しての実装をしなくてはならないので頭がパンクしそうにも……。

しかし iOS 側とサーバーサイドを一人で実装したおかげか、コミュニケーションコストも特になく素早く出来たほうだと思っています。

Webhook 対応

Webhook のヘルプページにエンジニアからレビューをもらう

日本でユーザインタビューをした時は出なかったのですが、台湾のユーザから Webhook の要望が多かったので実装することにしました。

Webhook の仕様は GitHub や esa を参考にし、セキュリティ強化のための ペイロードの署名を送る仕様にも対応させました。

使用例としては Slack が一番多かったのですが、Zapier と連携して JIRA に投げたりと皆さん色々やられていたようです。

ここで苦労したのはヘルプページの作成とカスタマーサクセスチームとの連携でした。
Webhook という機能は主にエンジニアが使うもので、エンジニア以外は普段目にするものではありません。
なのでカスタマーサクセスチームに理解してもらうように説明をしたり、パラメータなどのヘルプページはこちらで大まかなものを作成したり、 それを Prott チームのサーバサイドエンジニアに実際使う側の立場になってレビューをもらったりしました

SDK の大きな仕様変更

iOS の Balto SDK は Balto 本体アプリとアカウント連携する仕様だったのですが、これが非常に多くの問題を引き起こしていました。
トラブルが多く問い合わせの数もかなり多かったです。

そこで無理にアカウント連携させるのではなく、SDK 自体にログイン機能を設けることにしました。
ユーザに楽をさせようと思った結果が、逆になっていた典型的な例ですね

大きな仕様変更だったので、こちらもこのタイミングでゼロからベースのリファクタをしました。

iOS Extension 対応

Balto のウィジェット

Notification Extension (リッチ通知) や Today Extension (ウィジェット) などに対応をする際、アプリ本体のコードを共有するために Clean Architecture で分けたレイヤーを Framework 化して共有できるようにしました。

この時 Clean Architecture でリファクタしておいて良かったなと実感。

しかし Framework で分けるにしても気にしなくてはならないことも多く、メソッドやプロパティのアクセスレベルにより一層気をつけたり、何故か配布したベータ版だけクラッシュしたりと、意外に時間を使ってしまいました。

振り返って

まだここに書いていないこともたくさんあることを考えると、本当に色々やってきたなと思います。

技術的な面でもサービスをゼロから作っていくのは本当に楽しいですが、その分苦労も多いです。
どんなに気をつけても技術的負債は出るし、選択した技術が失敗することもあります。

Balto チームはエンジニアもエンジニア以外のメンバーもそれを理解し、適宜サービスの成長のために負債に目をつぶったり、でもいつか必ず直そうと自発的に裏でやっていたり、直すときには快く承諾してくれる PM やデザイナがいたり、良いメンバーに恵まれたなとつくづく思いました。