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