この記事は、比較的規模の大きなサービスのネイティブアプリにプッシュ通知機能を導入する風景を淡々と描いたものです。過度な期待はしないでください。なお、一応お約束として書いておくと、この記事は個人の見解であり、所属する組織の公式見解ではありません。
Amazon SNS や Firebase などのプッシュ通知配信基盤のおかげで、iOS/Android 端末へのモバイルプッシュ通知の実装自体は簡単にできるようになってきました。これらのサービス基盤の利用にはサーバーサイドの知識はほぼ必要なく、普段ネイティブアプリ開発しかやっていないような方でも簡単に利用することができる作りになっています。しかしながら、実際に比較的大きなサービスにモバイルプッシュ通知機能を導入・運用していくにあたっては、地味に考慮しなければならない点がいくつも見つかりました。
Amazon SNS を利用したプッシュ通知の実装方法自体については公式のドキュメントをはじめとして、Qiita にも記事がゴロゴロ転がっているのでお好きなものを参照していただくとして、この記事では大規模サービスのネイティブアプリにプッシュ通知機能を導入する際に考えなければならないことをまとめていくことにします。
プッシュ通知機能は諸刃の剣です。 ユーザーに対してサービス側から好きなタイミングでメッセージを送ることができる一方で、不要な通知はユーザーの気分を害します。そしてそれは、通知をオフにされたり、アンインストールされたりといったことに繋がるでしょう。
ネイティブアプリにプッシュ通知機能を導入する際にまず考えなければならないことは、「なんのためにどんなプッシュ通知を送るのか」 を決めることです。非常にマジレス的でつまらない、当たり前の内容ですね。
しかし、世の中には意味不明なプッシュ通知を大量に送ってくるアプリがあり、私も日頃アンインストールしまくっているので、こうした当たり前のことを検討していないか、ユーザーへのいやがらせのためにプッシュ通知を送って楽しんでいる悪趣味な方がたくさんいるような雰囲気を感じます。
さて、とあるところに作品を投稿できるサービスがあり、ネイティブアプリから作品が閲覧できるシステムがあったと仮定しましょう。ユーザーはきっと、気に入った作品が更新されたら通知が欲しい のではないでしょうか。また、サービス内の突発的なイベントや面白い作品の情報などについても通知が欲しい というケースがあると思います。
ここで登場した 2 つの通知パターンについて詳しくみていくと、特徴が違います。
前者は、ある作品をお気に入り登録しているユーザーにだけ通知がいく個別通知になります。一方後者は、任意のタイミングで全ユーザーに通知がいく全体通知になります。そして、個別通知は比較的定形のメッセージ、全体通知は自由なメッセージを送りたいケースが多いのではないでしょうか。まとめると以下の表のようになります。
種別 | 内容 | 送信先 | 通知発生タイミング | メッセージ形式 |
---|---|---|---|---|
個別通知 | お気に入り作品の更新情報など | 個別のユーザー | サービス運営者による制御が難しい | ほぼ定形 |
全体通知 | イベントやお知らせなど | 全ユーザー | サービス運営者により制御できる | 自由 |
世の中にたくさんのアプリが存在しますが、実はどんなアプリも通知のパターンとしては、だいたいこのようなものに落ち着くのではないでしょうか。
送りたいプッシュ通知の内容を分類した結果をみると、いくつか検討が必要な項目が見当たります。それぞれについて見ていきましょう。
ユーザーが作品を投稿できるようなサービスの場合、そのイベントの発生時刻をサービス運営者が制御することは不可能といって良いでしょう。これは、真夜中にユーザーのデバイスに通知が行ってしまう可能性があるということを意味します。
こうした場合、ユーザーのデバイスにプッシュ通知が到達したあと、すぐにメッセージを表示せずに溜めておき(サイレントプッシュ通知)、定時にユーザーに知らせるという手法を検討すると良いでしょう。
一般的な方はアプリを
などに使うことが多いのではないでしょうか。このあたりは各アプリのユーザーの特性によって大きくかわる部分ではありますが、いずれにせよユーザーがいつ通知を欲しがるかを検討することは必要かと思います。
そして必要であれば、サービス運営者が制御できないイベント通知に関してサイレントプッシュ通知を利用し情報をデバイスにスタックしたのちに、ユーザーへ定時にメッセージを出すことを検討してみてください。
全体通知として、サービス運営者がメッセージを自由に入力し、アプリ内での遷移先を自由に定めることができる仕組みを作った場合に考慮しなければならないことは、オペレーティングミス です。人間が自由に操作できる部分が大きければ大きいほど、この手のミスは起こりやすいでしょう。また、プッシュ通知は一度送信してしまうと、取り消しは基本的にはできない ため、ミスに対する取り返しはほぼつきません。手が滑って適当なメッセージを送ってしまえば、炎上の要因にもなりうるでしょう。
こうしたことは、送信予定のメッセージをあらかじめサービス運営者のみに送信できる仕組み を作れば、ある程度防ぐことができるのではないでしょうか。サービスの規模や特性などにもよりますが、プッシュ通知の事故のリスクとテストメッセージ送信の仕組みを天秤にかけ、必要かどうか判断することになるかと思います。
以上のことを検討した結果、次のような機能を実装する必要があると仮定しましょう。
以降はこれらの機能を Amazon SNS を用いて構成していくことについて考えていきます。
いよいよプッシュ通知システムの実装方法について検討していきます。ここまで技術的な話があまりなかったので、ただのポエムに成り下がるところでした。危ない...。
Amazon SNS を用いて iOS/Android デバイスにプッシュ通知を送るためには次のような手順を踏む必要があります。初見の方にはよく分からない単語が出てくるかと思いますが、ひとまず流れだけを見てみてください。
図を見ながら順を追って説明していくと、まずアプリ起動時に iOS アプリであれば APNS に、 Android アプリであれば GCM にデバイストークンを払い出させます。これは(各アプリごとに)デバイスを一意に特定するためのトークンです。
続いてそのトークンをサービスの API サーバーなどへ送出します。この部分についてはアプリに AWS SDK を入れてしまい、API サーバーを通さない実装もできるかと思います。しかし個人的には、プッシュ配信基盤をいつでも差し替えられるようにしておきたいので、デバイストークンをどう処理するかについてサーバー側が握れる仕組みの方が好みです。
API サーバーは、受け取ったデバイストークンを Amazon SNS 上に作った application
へ登録し、払い出された endpoint_arn
を受け取ります。application
はプッシュ証明書等と紐付いた概念 であるため、iOS/Android でそれぞれ別のものを作成する必要があります。また、application
には通知を送りたい対象のデバイストークンを登録する必要があります。登録を行うと endpoint_arn
というものが発行されます。これはデバイストークンと 1 対 1 に紐付いているものになります。デバイストークンのフォーマットは iOS/Android で異なるため、 AWS 側で扱いやすいように統一的な名前を発行しているのではないかと思います(※個人の感想です)。こうして払い出された endpoint_arn
と デバイストークンはユーザー情報と紐付けて永続化しておきます。
個別通知を送るだけであれば、application
にデバイストークンを登録し、endpoint_arn
を払い出すところまでで十分なのですが、全体通知をするために、今回は Amazon SNS のトピックという機能を使うこととしましょう。トピックは subscriber
を要素として保持しており、トピック宛てにメッセージを送ると subscriber
全員にメッセージを配信してくれるという機能を持っています。したがって作成したトピックを、払い出されたすべての endpoint_arn
に subscribe
させれば、全体通知を実現することができます。
以上が Amazon SNS を利用したモバイルプッシュ通知のための下準備になります。続いて実際に通知を送信するときの流れについてざっくりと見ていきましょう。
図のように、個別通知の場合は DB からメッセージを送りたい対象ユーザーの endpoint_arn
を拾い、そこ宛てにメッセージを通知します。全体通知の場合はトピックに対してメッセージを通知します。すると Amazon SNS は APNS や GCM などの各プラットフォームの通知配信サービスへとメッセージを届けてくれます。そこから先は APNS や GCM がよしなに各ユーザーのデバイスへの通知を届けてくれるでしょう。
ひとつ注意が必要なのは、デバイストークンは一定期間を経ると無効になるということです。無効になるタイミングは APNS/GCM 次第であるため、アプリ起動時には毎回デバイストークンをサーバーサイドに送り、endpoint_arn
を払い出すようにする必要があります。また無効になったデバイストークンに紐づく endpoint_arn
へメッセージをを送ると当然通知は失敗するのですが、その際に endpoint_arn
が持つ属性である Enabled
が false
になった状態で、 application
に残り続けます。放置し続けてもよいのですが、たまり続けると実装方法によっては個別通知のパフォーマンスが低下したりなど問題が起こりうるので、なんらかの削除機構を作るのがよいのではないでしょうか。
以上が Amazon SNS を利用したモバイルプッシュ通知のざっくりとした流れになります。
Amazon SNS を用いたモバイルプッシュに関する基礎知識を確認したところで、前節で想定した次のような機能要件を実現するシステム構成の全体像を見てみましょう。ただし、サービスは基本的にオンプレミスで動いていることを前提とします。
機能要件
- 特定ユーザーへイベントごとの定形メッセージを通知する機能(個別通知)
- アプリ側でメッセージの出現タイミングを制御する機能(サイレントプッシュ+定時通知)
- 全ユーザーに対し、自由なメッセージや遷移先を指定した通知を送る機能(全体通知)
- 事前にサービス運営者にのみにテスト送信・動作確認できる機能(通知テスト機能)
ご覧のように、結構複雑になります。すべて AWS 上に乗っかっていたり、Direct Connect などを利用しているのであれば、もう少しシンプルな作りにはできる部分はあるかと思います。あと、図中には登場していないのですが、ネイティブアプリ側のプッシュ通知処理実装はもちろん必須になります。
図中に登場した 6 つのモジュールの実装についてそれぞれ見ていきます。
ネイティブアプリからデバイストークンを受け取るエンドポイントを作成します。適当なパスを切り PUT
で受け取るような形になるかと思います。サーバーはデバイストークンを受けとったあと、その処理は他のジョブに任せてさっさとレスポンスを返してあげましょう。そのためにキューにデバイストークンとユーザー ID とプラットフォームタイプ(iOS/Android など)の情報を含んだジョブを登録します。
オンプレ環境にすでにジョブキューがあるのであればそれを使い、なければ Amazon SQS の料金が激安なので利用してみるのも手かと思います。
ユーザーから受け取ったデバイストークンの処理を受け持つバッチジョブを作ります。このジョブがしなければならないことは以下の 4 つになります。
application
にデバイストークンを登録し、endpoint_arn
を払い出させるendpoint_arn
に subscribe
させるsubscribe
させるdevice_token
と endpoint_arn
をユーザー情報に紐付けて永続化させる1.
の手順については Amazon SNS の CreatePlatformEndpoint
という操作を行うことになります。また、2.
と 3.
の手順については Subscribe
という操作を行うことになります。詳細についてはドキュメントを確認してみてください。
既存の稼働しているサービスへの作品更新時に、プッシュ通知送信ジョブを作成し、キューに積んであげるように変更をいれましょう。この部分はきっとほんの少しの実装で済むのではないでしょうか。
このジョブは、作品をお気に入りに登録しているユーザーの endpoint_arn
のリストを取得し、ペイロードを並列で publish します。これには Amazon SNS の Publish
という操作を行います。個人的にはこの Publish
の宛先である endpoint_arn
をリストで指定できるように AWS 側に機能追加してほしさはあるのですが、いまのところ 1 回の publish で 1 つの endpoint_arn
しか指定できません。今後に期待しています...。
ある程度の規模以上のサービスであればきっと、サービスを管理するツールは存在すると思います。これまで見てきたように作品更新通知についてはバッチジョブ連携により自動的に送信される仕組みになっています。しかし、イベント情報などを知らせる全体通知に関しては、サービス運営者の手によってメッセージや遷移先の指定をする必要があるため、サービス管理ツールにフォームを追加する必要があるでしょう。
オペミスを防ぐために、最初にテスト通知を送り、確認したものしか本番送信できないような仕組みづくりなど、工夫次第で手間のかかりぐあいはだいぶかわる箇所でもあります。
また通知の即時送信の仕組みしかないと、サービス運営者は決まった時間に送信ボタンを押さなければならず、運用コストは大きくなりそうです。必要であれば通知を予約する仕組みなどを作ると良いのではないでしょうか。
最後に無効になったエンドポイントを削除するジョブを実装しておきましょう。Amazon SNS の application
が持つ endpoint_arn
には、その属性が変化したときに SNS トピックに通知してくれる機能があります。 application
の設定画面の以下のような部分に通知先の SNS トピックの ARN を設定する項目があるはずなので、そこを開いて disabled なエンドポイント削除トリガー用トピックの ARN を指定してあげてください。
続いて実際に削除処理を行うラムダ関数を用意しておきましょう。こちらはラムダ関数に飛んできたデータの中から endpoint_arn
を抜き出し DeleteEndpoint
という操作をすれば OK ですので、適当にラムダ関数を実装してください。
ラムダ関数の実装が終わったら、 disabled なエンドポイント削除トリガー用トピックに、作ったラムダ関数を subscribe
させれば、 disabled になった瞬間にラムダ関数が走り、エンドポイントが削除されます。大きなサービスで全ユーザーにプッシュ通知を送ったりすると、当然無効なエンドポイントもたくさん生まれるので、もしかすると AWS Lambda の同時実行制限に引っかかってしまうかもしれません。そのあたりは調整して、場合によっては上限緩和申請を行うとよいでしょう。
クラウドサービスが手頃な値段で誰でも簡単に利用できるようになってきた今、「アプリ開発エンジニア」や「サーバーサイドエンジニア」といった肩書きは意味をなさなくなってきています。思い立ったらすぐに iOS/Android アプリ開発者が API やプッシュ通知などサーバーサイドの機能に手を加え、運用していくことができるようになってきました。
しかしながら、それなりの規模のサービスに導入しようとなると、まだまだ検討しなくてはいけないことが多いということも事実でした。今後ますますクラウドサービスが発展していっても、人間が考える余地は残り続けるはずなので、クライアント/サーバーの知識をバランスよく摂取して、ユーザーに快適に利用していただけるサービスづくりをこれからも進めていきたいですね。
なお、今回検討した内容の一部は、この秋開催された AWS のカンファレンス Re:invent にて発表された Amazon Pinpoint という新機能を用いるとよりきれいに実装できたり、もっと高機能にできる可能性があります。このあたりについては引き続き調査を行い、今後の改善につなげていきたいと考えています。
偉そうに記事を書いてますが、以上のことは自分一人で考えたわけではないので、一緒にお仕事をしたチームの方々に感謝の気持ちを記しておきます。今年も一年間、楽しい開発ライフをありがとうございました。
以上、ドワンゴ Advent Calendar 2016 23 日目の記事でした。明日は @kwappa さんの記事です。楽しみにしております。
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです