スマホアプリにおける「強制アップデート」は、万能じゃないよという話
なぜ書こうと思ったか
仕事をしている中で、「強制アップデートするので、バックエンドAPIに破壊的な変更が入っても大丈夫だよね?」的な雰囲気を感じたので、時系列を明確化することで、「そうじゃないよ」ということを伝えられるかな?と思ったので、整理します。
("メンテナンス状態に入れっぱなしにして、ユーザーが数日間程度利用できない"といったことが、業務上OKなのであればもっとシンプルに運用できますが、多くのアプリでは許されないと思っています。)
前提
- App StoreやGoogle Playに公開するアプリを前提としています。
- アプリ専用のバックエンドAPIが存在する前提です。
強制アップデートとは
アプリ起動直後や、アプリ上で何か操作したときに、「アプリをアップデートしてください」といったダイアログが出て、アップデートする以外のアプリ操作を受け付けなくなるものです。
これにより、アプリ提供側は、例えば「前のバージョンを利用されると、壊れたデータがどんどん作られてしまうため、新しいバージョンのみに利用を制限したい」といったケースに対応できます。
バックエンドAPIの破壊的変更とは
通常、スマホアプリ側の実装では、様々な前提の上でバックエンドAPIを実行しています。
例えば、以下のような前提を置いており、それが変わってしまうと想定外の事態が発生します。
- 前提:APIレスポンスはJSON形式であり、XXXというキーは必ず返却される。
- →そのキーが返却されない、nullが返却されるケースはそもそも想定していない(クラッシュするかもしれない、不正な値が表示されるかもしれない)
- 前提:必須のリクエストパラメータはYYYのみであり、それ以外は指定しなくても正常なレスポンスが返却される
- →突然ZZZという必須パラメータが増えると、そのAPIに対するリクエストは必ず失敗する
(このような前提を置かずに実装しようとすると、「現在のAPIだと発生し得ないエラー処理や、デフォルト値への置き換え」を実装しなければならず、処理が複雑化、結合時のテストもできないためにその実装の正当性も確認できません。)
強制アップデート発動までの時系列
(この流れを理解してもらうことで、「強制アップデートと言っても、段取りをちゃんと考えないと」と思ってもらえると思います。)
通常、以下のようなフローになると考えています。
- バックエンドAPIの新規バージョンを、本番環境にデプロイ
- アプリの新規バージョンをストアに申請
- アプリが審査を通過し、一般公開を実行
- 順次、ユーザーのストアアプリにアップデート通知が配信
- 強制アップデート発動(全ユーザーがアップデート可能になったら)
- 旧アプリからのアクセスを想定しないバックエンドAPIを本番環境にデプロイ可能
仮で日付を入れて図示すると、以下のようなイメージです。
問題点1: 新旧両方のアプリが、バックエンドAPIを利用するタイミングが必ず存在する
アプリの審査を行うためには、新規のバックエンドAPIをデプロイしておく必要があります。
ただし、審査中ということは、一般のユーザーはその新しいアプリをインストールできない状況(古いアプリを利用している状況)です。
これにより、新旧両方のアプリが、バックエンドAPIを利用するタイミングが必ず存在します。
問題点2: すべての一般ユーザーが、同じタイミングで新しいアプリを利用可能になるわけではない
ストアのアプリ審査が無事通過すると、アプリ提供側は「公開」ボタンを押すことになります。(審査通過後に自動的に公開状態にする設定もありますが、結果的には同じ状況になります。)
ただし、それを押したからといって、"即座に" "すべての"ユーザーが、その新しいアプリを利用できるわけではありません。
Google Play で公開されているアプリを見つけられない
アプリやアップデートを最近公開した場合は、Google Play に変更内容が反映されるまでに数時間かかることがあります。
App が承認されてから App Store で配信されるまで、最大で 24 時間かかります。
(5年以上前の経験ですが、公開ボタンを押してから、開発メンバーでストアアプリのアプリ更新を定期的にチェックしましたが、人によっては数分でアップデート可能になりましたが、3,4時間後にようやくアップデート可能になるメンバーもいました。)
そのため、「公開」ボタンを押した直後に強制アップデートを発動すると、
「現在のアプリはダイアログが出てて利用できないけど、案内に従ってストアに行っても新しいアップデートなんて存在していない」
といった状況が発生し、ユーザーの混乱を招く可能性があります。
解決策
スマホアプリ向けのバックエンドAPIにて、単純に破壊的変更をデプロイすることができないことは理解頂けたかと思います。
考えられる解決策を、いくつか列挙していきます。
1. 先にアプリ側を更新する
例えば、「あるレスポンス項目の項目名を、XXX
からYYY
に変更したい」といったケースでは、以下のような流れで対応できます。
(説明しやすい、発生しやすいケースを思いつきませんでした。。。)
- アプリにて、「
YYY
という項目があればそれを利用し、なければXXX
を利用する」という更新を行い、配布する - 一定期間経過後、強制アップデートを発動する
- この時点で、
XXX
という項目を利用するアプリが存在しなくなる
- この時点で、
-
YYY
に変更したバックエンドAPIをデプロイする - アプリにて、上記の処理を単純に「
YYY
を利用する」という形にして、配布する
2. バックエンドAPIを段階的に更新する
例えば、「あるレスポンス項目が、日付文字列を返却していたが、unix timeを返却する形に変更する」といったケースであれば、以下のような流れで対応できます。
- 既存の項目が
date
という項目だった場合、新しい形式のものをunix_time
という別の項目で返却するように、バックエンドAPIを変更してデプロイする - アプリ側で
unix_time
項目のみを利用するように変更、公開する - ユーザーに新しいアプリが行き渡った頃に、強制アップデートを発動する
- バックエンドAPIから
date
項目を削除して、デプロイする
3. APIをバージョンごとに切り分ける(または、別のAPIエンドポイントにする)
具体的には、バックエンドAPI側(もしくは、その手前のインフラレベル)で切り分けてしまう方法です。
- アプリ側から、リクエストヘッダにて「利用したい、APIのバージョン」を指定して、バックエンドAPIでは複数のAPIバージョンを受け付けられるようにしておく
- アプリ側から、リクエストヘッダに「アプリバージョン」を含め、バックエンドAPI側で処理を分岐させる
- 既存APIとは別のパスにデプロイして、一時的に両方をアクセス可能にする(例:
/me
と/me_new
を並行運用する)
バックエンドコードの複数世代を管理する必要が発生するため、DB migrationなどが発生すると、両方を更新する必要があったり、
そもそも、それらのバージョンをいつ削除できるかなどを管理する必要が出てきて、
管理コストは大きくなってしまいます。
(個人的には、他の解決策で対処できなかったときの最終手段だと思っていますが、わかりやすいので導入されるケースが多いのが悩ましい)
ちなみに、Webアプリはどうなんでしょう?
最近のWebアプリにおいても、似たような問題は発生するのでは?と感じています。
- CDNなどのインフラレイヤーにおける、フロントエンドコードのキャッシュによって、ユーザーに配布するコードを管理しづらくなっている?
- PWA文脈で、Service workerによるオフライン動作(そのためのローカルキャッシュ)が実装され、ユーザー環境で動作するコードが即時更新できない?(特に、ネットワークが切れたり繋がったりする環境とかで)
このあたり、個人的には、このあたりの「バックエンドAPIとのアップデートタイミングが、、、」といった悩みを見かけない印象ですが、
私の観測範囲の問題なのか、そもそもあまり利用されていないのか、うまく回っているのか。。。
まとめ
アプリの強制アップデートができるようになっていたとしても、バックエンドAPIの破壊的変更を行うことが難しいということが、理解頂けたかと思います。
強制アップデート自体、ユーザーにとってはストレスとなり、離脱や低評価のリスクとなります。
うまくバックエンドAPIを更新していくことで、そのリスクを低減できるかと思います。
(そのアップデートを公開してから例えば半年後に強制アップデートを発動したら、自然にアップデートされているユーザーが多数派となっており、強制アップデートがあったことを認識するユーザーがかなり減っているはず。)
また、このように、スマホアプリの提供する場合、アプリ開発側ではどうにもならない「ストア側の制約」が存在します。(審査が必要、配布が浸透するまで時間がかかる)
こういったことを、ビジネス側やプロジェクト進行側(Project managerなど)にも理解いただくことで、無理のない計画立案ができるかと思います。
Discussion