半年で250回のリリース!? FiNCアプリのオンボーディングMicroserviceの裏側
運用工数を1/240にするために3年越しのコードを3ヶ月で全部リプレイス
FiNCアプリは数十ものMicroservicesに分かれていますが、アプリを初めて利用した時に開始されるオンボーディングも、1つのMicroserviceとして存在しています。
FiNCのオンボーディングは、会社の継続率/LTVの大部分を支えている
実はFiNCアプリのオンボーディングは、内容をチューニングしただけでもアプリの継続率を1.5倍~2倍にしたり、会社の大部分の売り上げを支える企画をいくつも実現するなど、数ある中でも最も重要なMicroserviceになっています。
社内での運用ペースも凄まじく、実に半年で250回もの変更がリリースされています。リリースはデプロイを必要とせず、専用の管理画面から反映する作業は2~3分程度で行えます。
この記事ではそんなオンボーディングのMicroserviceをテーマに、以下のポイントについてまとめたいと思います。
- オンボーディング専用サービスを3ヶ月もかけて切り出した理由
- 既存システムのリプレイスという優先度の上がりにくい話を最優先にできた理由
- リファクタリング→リアーキテクト→リプレイスでYAGNIを守る
- 運用だけで可能なことを倍増させながら運用工数を1/240に抑えた方法
- DSLで実現する分岐システムのサンプルコードの紹介
- 最終的に全てのKPIを改善することができた結果
1.オンボーディング専用サービスを3ヶ月もかけて切り出した理由
ジムでの経験がオンボーディング重要視の元となった
FiNCはサービスが立ち上がった初期の初期から、オンボーディングは最も重要なステップの1つであるという認識が浸透していました。
これは社長自身が元フィットネスジムのトレーナー出身という経歴が大きく影響しています。とりわけパーソナル指導において、レッスンを始める前にお客様の身体測定やカウンセリングをしてから指導に入ります。そこでは体重や身長はもちろん、お客様の悩みから日頃の生活習慣に関わることまで、お客様と打ち解けながら理解していったそうです。(余談ですが、FiNCはアプリより先にパーソナルトレーニングジムを開業しています。)
こうした背景から、アプリUX設計においてもオンボーディングが重要であるという仮説がありました。そのため、初期からかなり自信を持って大胆に取り組むことができたのです。
負債を抱えるようになってしまったオンボーディング
しかし、数年にわたるサービスの変遷により、オンボーディングの仕組みはかなり負債を抱えるようになっていました。
- 昔からあった中央サービスに大量に継ぎ足されたコード
- 後からABテスト検証基盤を入れた関係で別のサービスが割り込み
- 設定が複雑すぎてオンボで何が出てくるのか正しく把握するのも大変
- オンボ内容変更したいときはマスタ作業に半日、QAと修正に丸一日、エンジニアのデプロイ待ちに半日で丸2日かかる
- 障害やエラーが起き、その原因調査に時間がかかり過ぎる
- 分析用のログデータがうまく取得できず、曖昧な検証が進む
紛れもなく今後もオンボーディングの重要性は変わらず、むしろ運用頻度が上がることは目に見えていたため、上記の問題解決と共に、サーバーアプリケーションのリプレイスを行うことを決めました。
2.既存システムのリプレイスという優先度の上がりにくい話を最優先にできた理由
あらゆる開発案件が社内に存在する中で、こうしたサーバーアプリケーションのリプレイスを行うという意思決定をするのは、実現ハードルがあると感じる方もいるかもしれません。実際今回のプロジェクトもリプレイスではなく既存拡張できないかみたいな提案は何度もされました。
意思決定はユーザーインパクトで行われる
結論から言うと、FiNC社内では意思決定は最終的なユーザーインパクト軸で行われることが多く、今回もそれを整理比較、伝達する形でリプレイスが意思決定されました。
重要なのはインパクトがどのように導かれるかです。シンプルにいうと、「期待されるKPI改善幅/開発にかかる工数=ROI」が解となりました。
期待されるKPI改善幅をどのように仮説立てるのか
これは元々の仕組みをどうにか駆使し、小さな実績を積み上げたことで、その運用工数がどれくらい削減できるのか、また障害などによる損失がどれくらい減るのかを定量的にまとめることが出来ました。そして何より重要なのは、既存の運用で実現できないことも、リプレイスを行うことでどれだけ新しいことが実現しやすくなるかを説くことでした。(実現した内容はあとで詳しく述べます)
エンジニアなら誰でも共感することですが、負債の上にコードを足すよりもきちんと整理された物の上に開発する方が早いのは明らかです。課題は、それらを数値化するなどわかりやすい形で証明しコミュニケーションを取れるかどうかだと思います。
開発工数を見積もることのメリット
正直これはコツなどはあまりなかったのですが、とにかく周りのエンジニアとサーバー/クライアント問わず設計の相談をして、あとはコード書くだけといった状態まで来れたのが大きかったです。何度もフィードバックと再見積もりを行うことで、数ヶ月の開発でもリリースタイミングをコントロールできました。これによってリプレイスによるオンボ施策の長期間のブランクも企画サイドに待ってもらうことができました。新機能とそのリリーススケジュールは、ビジネスサイドとコミュニケーションを取りやすくする最大のツールです。
ユーザーインパクトvs実現工数の感覚をバランス良く持つ
紹介が遅れたのですが、僕自身(元々)Rails/iOSエンジニアであり、当時の主な役割はデータ分析とグロースハック企画でした。そうした自身のバックグラウンドを活かし、ユーザーインパクトと実現方法の両面を理解していたこともPJを進めやすくする要因だったと思います。もちろん分析と開発両方の役割を持っているという方でなくとも、そうした役割のメンバーと協力しあって一つのPJを実現することはとても大事なことだと思います。(もちろん、両方やりたいという方も全力で応援します!)
3.リファクタリング→リアーキテクト→リプレイスでYAGNIを守る
長く使われる運用システムに求められることは、運用サイドだけで実現できる幅の広さ、障害の起きにくさなど多岐に渡りますが、どれをとってもやろうと思えばどこまでもできてしまいます。拡張性を意識しすぎて一度も拡張されない機構を組んだり、テストを厚くした割には想定外の障害が起こるなど、よくあることです。
それらをニーズぴったりに作るためには、初回のリリース以後必要に応じて継ぎ足し→リファクタリング→リアーキテクト→リプレイスをすることだと思います。今回はその典型的な実例でした。具体的な歴史を交えながら、その変遷をご紹介します。
a.オンボーディング初期の紆余曲折
最初の頃は、実はまったく異なる形のオンボーディングでした。
この頃は、マスタによる運用の仕組みなどほとんど無いに等しく、ほぼコード管理でした。何をチューニングすればKPIが上がるのかもわからず、工数を犠牲にしながらも、最も良いオンボーディング体験が可能なUI変更を度々行っていました。
そうした紆余曲折の中で、現在のチャットUIのオンボーディングに落ち着きます。
b.リファクタ期
チャットUIに落ち着いた頃、KPIを改善するにはオンボーディング内容と進行順を変えられることが重要ではないかという仮説がありました。
そこで少なくとも順番は変えられるように、進行単位を1つのリソースのようにみなして、進行順番を入れ替えられるようなAPI設計になりました。このときはまだ単一サービスのソースコード変更で乗り切っています。
しかしオンボーディングの進行について変更する仕組みはできたのですが、異なる時系列同士のデータ比較に限界を感じたため、ABテストの仕組みを導入することになりました。
c.リアーキテクト期
既存のデータベース構造から、コードのリファクタリング程度では出しわけを実現することが不可能でした。そこで、途中ABテストを行いたい箇所だけ別サーバーを経由し、また元のオンボーディングフローに戻るというアーキテクチャ変更がありました。数週間程度のミニマム実装での対応です。
2つのサーバーアプリケーションを横断することによって、障害原因がわかりにくくなったり、変更作業でカバーする範囲が増えるなどの問題はありつつも、この仕組みは重要な指標を残すことができました。文言、画像等でどれだけKPIを改善することができるかなど多くの指標を溜め込みつつ、運用における課題点も把握することができたのです。(この仕組みはなんだかんだ1年ほど使われました。)
僕自身この時点まではオンボーディングの開発に一切関わっていませんが、分析官としてこの仕組みを運用するメンバーと一緒にABテスト検証を重ね、不具合などの問題が起きたら調査を手伝うようになっていました。この経験がリプレイスの設計アイディアのほとんどを生むことになりました。
d.リプレイス期
これまでの変遷を踏まえて、解決しなければならない課題、実現しなければならないものを明確に持つことができました。
最終的に重要とされたポイントは以下です。
- ユーザーごとのパーソナライズができること(進行分岐)
- 運用コストが低いこと
- 予防/検知とともに障害に強くなること
- これらを単一のMicroserviceとして構築し、メンテナンスできること
リプレイスの結果、実現した仕組みは以下の項でご紹介します。
4.オンボーディング進行分岐の仕組みの設計
分岐の仕組みは、主にインストール時の流入経路やユーザーのデモグラ情報によって表示するコンテンツを出し分けるために使います。
例えば、キャンペーンサイトから流入したユーザーにはその期待に沿ったコンテンツが表示されるようになっています。
こうした分岐を作るためには、まず進行単位を定義しました。「会話文の吹き出し不特定多数 + ユーザーの回答選択アクション」を1つのsceneとしてみなすことで、scene単位で進行の順序と分岐をコントロールしやすくなります。
この分岐をコントロールするために、マスターデータにどのように記入させるかが悩ましい点でした。はじめに考えたのは、DSL(ドメイン特有言語)を定義し、それを直接マスターデータに書き込む形です。
しかし、結論それは止めることにしました。それは過去の経験から、運用が難しいシート設計だと取り込み工数が膨らむこと、またはバリデーションを追加する工数が膨らむこと、当時DSLをすぐに扱えるほど熟練した運用者がいなかったこと、そして決め手はDSLを直接書かずともあらかじめ10数個程度の分岐パターンを用意すれば事足りると過去の経験からわかったためです。
最終的には決められた分岐ロジック名を扱う形をとりました。DSLは代わりに、ソースコード内で定義されています。(condition_nameはDSLの呼び出しを担当しています。)
これらが基本的な仕組みとなり、順番を入れ替えたり、ABテストの設定を簡易に行うことを実現することができました。
また、地味なポイントなのですが、マスタデータの記入シート数は限りなく減らし2枚だけで作業が完結できるようになっています。時折、正規化されたRDBをそのまま書かせるようなマスタシートを見かけるのですが、記入のしやすさとしてはJOINできるものはJOINした方が良いでしょう。(正規化はDBだけで十分なのです。)代わりに取り込みスクリプトとバリデーション、取り込みエラーメッセージを用意しましょう。その紹介は下に続きます。
4.運用工数を1/240に抑えた方法
運用工数の圧縮は大事です。リリースサイクルが早まれば早まるほど、PDCA回数は増え、余った時間でよりクリエイティビティの高い仕事に取りかかれます。
記入が難しく内容に不整合が含まれてしまう原因
- 編集シート枚数が多い。
- シートを跨いだforeign_keyがInt型で提供され可読性が低い。
- カラムやシートを跨いだ不整合のエラーが起こる。
- 利用できない値を入れてしまう。
- 何が間違っているのかがわかりにくい。
記入が簡単で不整合を生まない方法
- マスタシートはできるだけシートをJOINし枚数を減らす。
- マスタ上ではforeign_keyをInt型ではなくString型で都度命名をしてもらう。(DB上ではforeign_keyにInt型カラムを利用しています。)
- DBレベルでの制約を必ず入れる。
- マスタシートのバリデーションとエラーメッセージを追加する。
エラーメッセージはもちろんプログラムエラーをそのまま出すことはしません。メッセージ内容はカスタムし、想定していないパターンが見つかれば都度追加していました。
安全かつ最速のリリースを実現する仕組み
よくある問題は以下のようなものでした。
- 今現在何が設定されているのかがわからない。
- QA時にユーザーデータや端末などの前提条件を揃えるのに時間がかかる。
- 実機で確認中に問題が起きても何が原因かわからない。
- StagingとProductionで状況が異なりProductionでしか再現しないエラーが起こる。
管理画面GUIを用意
設定状況は管理画面で確認できるようにし、簡単に編集できるようにしました。
また、ユーザーデータなどの前提条件を揃えるためのデバッグ機能も提供しました。
カスタムエラーで障害ポイントを検知
オンボーディング進行中のエラーに関してはカスタムエラーを積極的に導入することで、例えばクライアントアプリのバグによって引き起こされたデータの不整合などに気付きやすい状態にしました。(過去の障害経験からどこにエラー通知を入れるべきかなどの勘所はありました)
確実に安全なマスターデータをリリースする
StagingとProductionの差分については、必ずバリデーションとQAを通過しないと本番にリリースできない仕組みにしたことで、マスタ作業による環境特有のエラーを0にできました。
こうした設計や細かい仕組みを取り入れたことで、膨大な作業やエラー、待ち時間などが生じた結果丸二日かかっていたリリースフローを、ほんの数分から数時間で終えることができるようになりました。
5.DSLで実現する分岐システムのサンプルコードの紹介
最後にコードの紹介もしておきます。分岐ロジックを実現するためのDSLサンプルコードになります。
DSLは最初のコードのcondition ~ do ~ endの部分で、あらかじめ定義したvariableを用いて条件を書くことができます。このconditionをblockごと保持しておくことで、取り出した任意のユーザーに対してinstance変数をevalすることができます。
今回のDSLを実装するにあたり、DSLの代表例であるfactory_botの開発で知られているthoughtbot社の記事「Writing a Domain-Specific Language in Ruby」を大変参考にさせていただきました。
6.最終的に全てのKPIを改善することができた結果
3ヶ月という決して少なくない時間を使って、リプレイスから新機能までを実現しましたが、作ってみて本当によかったと思います。実際、この仕組みを使うことで離脱率、継続率、LTV(売上)などアプリで最重要とされるKPIに全て貢献することができました。
具体的に行なっていることはこちらの記事でわかりやすくまとまっています。
また、これまで運用に追われていたチームがよりユーザー体験を作る時間を増やすことができました。あとは、殺伐としていたチームも心なしか雰囲気が明るくなりました。笑
前提があってこその成功方式
こうした記事を書いていますが、どのサービスでもオンボーディングをここまで作り込む必要はないと思いますし、内製ではなくSDKを用いたABテスト基盤を入れた方が良い場合もたくさんあると思います。
今回のPJはFiNCアプリが直面した課題とタイミング、それに対する解決手段が完璧にマッチしていました。逆にいうと、前提にあった最適解は必ずあると思います。
さらにヘビーなリプレイスが待っていた…次章に続く
FiNCアプリはまだまだ改善すべき点が多いです。それらの問題を解決するために、また別のテーマで大幅なリプレイスを行ったのですが、それは今回のオンボーディングよりも倍くらい歯ごたえがありました笑。いずれまた記事にしたいです。(より強化されたDSLをまた使うことになりました。笑)
もしFiNCでヘルスケアアプリの設計をしてみたい!かっこいい運用の仕組みを一緒に作りたい!という方がいましたら、ぜひお気軽にお話ししましょう〜