株式会社マクアケで、ネイティブアプリ開発チームのリーダーをしています、Kimura Ryohei です。 半年ほど前からマクアケにジョインし、先日、iOSアプリをリリースしました。

Makuake_ios_app

アプリはこちらからダウンロードできます。

Download_on_the_App_Store_JP_135x40

マクアケにジョインする前は、株式会社サイバーエージェントで FRESH! by CyberAgentのAndroidアプリを開発していました。

縁あってマクアケにジョインし、そこから未経験のサーバサイド開発に取り組むことになりました。 今回は、iOSアプリへのプッシュ通知送信基盤の実装という大きな範囲を任せていただき、実装しリリースすることができました。

以下が端末に届くプッシュ通知の例です。

push_notification_image

今回はこの経験から

1. Firebaseを利用したプッシュ通知送信基盤の実装で得た知見

2. 未経験の技術への挑戦にあたって手助けになること

についてブログを書かせていただきます。

MakuakeにおけるFirebaseを利用したプッシュ通知送信基盤

マクアケはネイティブアプリを開発するのは今回が初めてでした。 そこで新規にプッシュ通知送信基盤を実装しました。

まず、前提に関して記載し、その後に実装内容・知見を述べます。

前提:Makuakeでのプッシュ通知に対する要件

今回、サービス内の概念に基づいたセグメント別送信が求められました。 例えば、サービス内のイベントに連動したプッシュ通知を行う、というものです。

前提:Firebase NotificationsとFirebase Cloud Messagingの違い

Firebaseを用いたプッシュ通知を実現しようとした場合、2つの名称が出てきます。 Firebase NotificationsFirebase Cloud Messaging(以降FCM)です。 それぞれ異なる特徴を持つため、使う際には要件にあったものを選択する必要があります。

Firebase Notifications

  • pros
    • ほぼ実装なくプッシュ通知の送受信を行うことが可能
    • 開封率の測定などもデフォルトで可能
  • cons
    • サービス要件に合わせた送信対象ユーザの絞り込みはデフォルトでは不向き
    • APIがなく自由度が少ない

FCM

  • pros
    • APIが存在し、要件に応じたプッシュ通知の送受信を柔軟に行うことが可能
  • cons
    • 送信するサーバサイド、受信するクライアントサイド、ともに実装が必要

プッシュ通知をとにかく早く実現したいというニーズが有るならば、Firebase Notificationsが選択肢になります。 基本的な機能(開封率の測定など)があるため、シンプルな利用であれば本当に実装量少なく実現が可能です。

ただし、カスタマイズをするとなると実装内容が増えます。例えば細かな送信対象の絞り込みを行おうとすると、Firebase Analyticsにイベントを送り、そのイベントを元にユーザセグメントを作るというような流れになり、手軽に使えるといったメリットが薄れてしまいます。

また、APIが存在せず、例えばサービス内のあるイベントが発火したタイミングでプッシュ通知を送信したい、といったときに自動化ができません。

対してFCMは、サーバサイド、クライアントサイド、ともに実装が必要なものの、各種APIを活用した柔軟なプッシュ通知送受信が実現可能です。 マクアケではこのメリットを享受するためFCMを選択しました。

今回用いたFCMの機能に関して以下に説明します。

FCMのトピックを用いたセグメント別送信

今回サービス要件として、「セグメント分けしたユーザにプッシュ通知を送信したい」というものがありました。 FCMにはトピックという概念があり、APIを介したトピックの操作が提供されているため、 今回はこれを用いました。

トピックとはFCM上でのユーザをまとめたセグメントにあたる概念です。 また、FCM上ではユーザを認識するためにregistration tokenと呼ばれるトークンを用います。 このトークンを用いることでFCMサービス上でユーザをトピックに紐付ける事ができます。 いくつかの種類のAPIが提供されていますが、今回Makuakeで用いたものについて説明します。

トピックの作成・トークンの紐付け・トークンの紐付け解除

トピックの作成とトークンの紐付けには以下のAPIを利用しました。

Manage relationship maps for multiple app instances

https://iid.googleapis.com/iid/v1:batchAdd
Content-Type:application/json
Authorization:key=API_KEY
{
   "to": "/topics/movies",
   "registration_tokens": ["nKctODamlM4:CKrh_PC8kIb7O...", "1uoasi24:9jsjwuw...", "798aywu:cba420..."],
}

細かな使用方法はリンク先に譲りますが、これを用いることで

  • 複数トークンを一度にトピックに紐付ける
  • トピックが存在しない場合にトピックを作成する

が可能です。 また、トークンの紐付け解除には以下のAPIを利用しました。

https://iid.googleapis.com/iid/v1:batchRemove
Content-Type:application/json
Authorization:key=API_KEY
{
   "to": "/topics/movies",
   "registration_tokens": ["nKctODamlM4:CKrh_PC8kIb7O...", "1uoasi24:9jsjwuw...", "798aywu:cba420..."],
}

上記のリクエストで対象のトークンをトピック紐付け解除することが可能です。

トピックに対しプッシュ通知送信

作成したトピックに対するプッシュ通知の送信もAPI経由で行うことができます。 Firebase Cloud Messaging の HTTP プロトコル

例えば以下のようにして送信が可能です。

$ FIREBASE_SERVER_KEY=''
$ curl --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  https://fcm.googleapis.com/fcm/send \
  -d @firebase.json

firebase.json

{
    "to": "/topics/test",
    "priority": "high",
    "notification":{
      "title":"/topics/test title",
      "body":"/topics/test body"
    }
}

単一トピックだけでなく複数トピックを組み合わせた送信もサポートされています。 ただし、一度に用いることができる演算子の数は2つまでという制限があります。

トピックにメッセージを送信

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
  "condition": "'dogs' in topics || 'cats' in topics",
  "data": {
    "message": "This is a Firebase Cloud Messaging Topic Message!",
   }
}

FCMを使う上での注意点

前述の一度に用いることができる演算子の数は2つまでという制限のため、トピックを4つ以上使った範囲指定のプッシュ通知送信はできません。 Makuakeでは一部のケースで4つ以上のトピックを利用して範囲指定する要件があったため、別の方法を探しました。 FCMには複数トークンを指定した送信方法が用意されており、それを利用することで対応できました。

複数トークンを指定した送信

以下の方法で複数トークンを対象にプッシュ通知を送信することが可能です。

curl --header "Authorization: key=${FIREBASE_SERVER_KEY}" \
  --header "Content-type: application/json" \
  https://fcm.googleapis.com/fcm/send \
  -d @firebase.json

firebase.json

{
    "registration_ids": [
        "tokenA",
        "tokenB"
    ],
    "priority": "high",
    "notification":{
        "title":"title",
        "body":"body"
    }
}

ただし、一度に指定できるトークンの最大個数は1000個という制限があります。 そのため、一度に大量のトークンに対してプッシュ通知を送信したいという要件があった場合、APIへのリクエストを複数回に分ける機能の実装が必要になります。(Makuakeではこの機能を実装しました。)

アーキテクチャ設計

ここまでの調査により、実現に必要な作業は以下のようになりました。

  • FCMトークンをトピックに紐付けることができる
  • トピックを指定したプッシュ通知送信ができる
  • FCMトークンを指定したプッシュ通知送信を複数回に分けて行うことができる

これらを2つの機能に分けて実装することにしました。

  1. トークンをトピックに紐付けるトピック紐付け機能
  2. 送信内容によって、トピック送信とトークン指定送信を使い分ける事ができるプッシュ通知送信機能

また、実装にあたり以下のポイントに注意しています。

  • マイクロサービス思想を用いて、既存のwebサービス実装への依存度を下げたこと
  • スケーラビリティを確保したこと

アーキテクチャの全体図は以下のようになりました。
makuake_push_notification_architecture

トピック紐付け機能

これは図の以下の部分です。

makuake_push_notification_architecture_topic

ここで実現しようとした機能は以下になります。

  • FCMトークンをトピックに紐付けることができる

用いている ECSやSQS等の技術詳細に関しては今回割愛して、動作の流れを説明します。

まず、既存のwebサービス上で要件にあう適当な処理をフックし、SQSにトピック紐付け依頼を送信します。 SQSからはGolangで実装したワーカーが、SQSに対してポーリングを行いトピック紐付け依頼を受け取ります。

紐付け依頼には種類があり、ワーカー内で種類を判定し、必要なデータをDBより取得、トピックを生成し、 「トピックの作成・トークンの紐付け・トークンの紐付け解除」で説明したAPIを用いることで、FCMにトピックを作成、トークンを紐付けます。

上記の実装により、既存webサービス側からはプッシュ通知の詳細を意識することなく、決められたJSONをSQSに送信するだけでよいので、プッシュ通知機能への依存を少なくすることができます。

また、スケールが必要な際には、ワーカーの部分がスケールすることで大量の処理に対応することが可能です。

プッシュ通知送信機能

これは図の以下の部分です。

makuake_push_notification_architecture_notification

実現しようとした機能は以下です。

  • トピックを指定したプッシュ通知送信ができる
  • FCMトークンを指定したプッシュ通知送信を複数回に分けて行うことができる

処理の説明ですが、最初はトピック紐付け機能と同様で、既存のwebサービス上で要件にあう適当な処理をフック、SQSにプッシュ通知送信依頼を送信します。

さらに定期的にSQSからワーカーが依頼を受け取ります。 送信するプッシュ通知にも種類があるため、ワーカーが種類に応じた送信内容を生成し、SQSに送信内容を送ります。

その後Lambdaが起動し、SQSから送信内容を取得、「複数トークンを指定した送信」で説明したAPIを用いてプッシュ通知を送信します。

トピック紐付け機能と重なりますが、既存webサービス側の行うことは、プッシュ通知の詳細を意識することなく、決められたJSONをSQSに送信するだけです。そのため、プッシュ通知機能への依存を少なくすることができます。

スケールが必要な際にもトピック紐付け機能と同じように、ワーカーの部分とLambdaがスケールすることで大量の処理に対応することが可能です。

MakuakeにおけるFirebaseを利用したプッシュ通知送信基盤 まとめ

以上のようにプッシュ通知送信基盤を実現しました。 ポイントを改めてまとめます。

  • Firebase NotificationsとFCMには違いがあるため、要件と照らし合わせ技術選定する必要がある
  • FCMのトピック管理機能を用いることで柔軟なプッシュ通知送信を実現できる
    • ただし、多くのトピックから送信範囲を決定する場合には要注意。自前での実装が多く必要な場合も
  • SQS、Docker、Lambdaを用いることで、既存実装に低依存なプッシュ通知送信基盤を構築できた

未経験の技術への挑戦にあたって手助けになること

以上の実装を任せていただき、実装しきることができました。

私はマクアケにジョインする以前はAndroid開発しか主だった経験がなく、サーバサイド開発はまさに未経験の技術への挑戦でした。 今回は前述の設計とは別の設計指針で開発が途中まで進んでいたのですが、自分なりに設計を見直し、提案し、作り直す実装まで行いました。

今回の経験から、未経験の技術への挑戦にあたって手助けになると、私自身が感じたことを幾つか共有させていただきます。

個人として ―プラットフォームを問わない知識の活用―

エンジニアリングの知識の中にはプラットフォームを問わない(依存しづらい)ものがあります。

例えば設計に関する考え方です。

私はAndroidの開発において、アプリケーションアーキテクチャに注目しながら開発をしてきました。 その過程で得た、プログラム内を出来る限り疎結合に保つ、抽象化のレイヤを適当な場所に加える、というような考えは、今回のアーキテクチャ設計にも大きく活かすことができました。

チームとして ―支える、任せる、受け入れる―

今回の実装に着手し始めたとき、私はチームにジョインしてまだ1ヶ月程度でした。

業務知識の理解も浅く、しかもサーバサイド開発未経験のエンジニアに今回の実装を任せるのは、チームとしても不安要素だったように思いますが、 同じ開発チームで、インフラからスクラムマスターまで幅広く担当されていた吉田さん @kakakakakku 中心に、以下のようなサポートをいただくことで、自分の力を発揮できたかと思います。

支える

未経験の技術の場合には、新しい技術知識の習得が求められます。それを支えるために参考文献を教えていただいたり、適切な経験者をメンターとして連れてきて頂いたりということがとても助かりました。

※メンターを連れてくるということは難しい場合もあるかもしれませんが、大変に効果がありますので、ぜひ検討していただきたいことの一つです。

また基本的なことではありますが、日々質問しやすい環境を作るなどの信頼関係を築くことも重要だったと改めて感じます。

任せる

スピード感を持った開発が求められている状況で、新米エンジニアに実装を託すことは勇気がいることだと思いますが、 悩む時間のバッファもある程度許容するほど作業を任せることが、本人の成長につながり、ゆくゆくはチームを助けると感じました。

今回は大きな範囲を任せていただき、個人としての成長を実感することができました。

受け入れる

経験の浅いメンバーであっても有益だと思う提言は受け入れるという姿勢が重要だと思います。

今回は、「アーキテクチャを変更した作り直し」という大きな提言を受け入れていただいたことで、個人としての成長とチームへの貢献の両方を感じることができました。

以上のようなポイントが、未経験の技術への挑戦を手助けしてくれると思います。

まとめ

今回は以下に関して書かせていただきました。

  1. Firebaseを利用したプッシュ通知送信基盤の実装で得た知見
  2. 未経験の技術への挑戦にあたって手助けになること

お役に立てば幸いです。

マクアケは引き続きアプリ・サービスの改善に取り組んでいます。 ご興味のある方はぜひご連絡ください。

Kimura Ryohei
2015年新卒入社。Android(Java, Kotlin)→サーバサイド(Go, PHP)、iOS(Swift)。 Makuakeのネイティブチームリーダー。世界をつなぎ、アタラシイをつくっています。