APNsのDeviceTokenからFCMのregistrationTokenを再発行してPush通知の基盤を移行する方法

こんにちは、@yositosiです。今回は、Togetterの公式アプリにて、APNsへのPush通知にも対応したFirebaseCloudMessagingへのデータとシステム構成の移行が完了したので作業のログを残しておきます。

![Firebase_console.png](https://qiita-image-store.s3.amazonaws.com/0/53041/869f7942-534f-0cfa-f735-3b4e11a2c0d0.png)

Togetterが提供している公式アプリでは、2年前にReactNativeへのリニューアルのタイミングで、Push通知に関して、当時の時点で特定のmBaasなどを採用せずに、自社でTokenを管理し、且つ開発の工数を減らせるような工夫として次のような構成を採用しました。

〜2018年5月まで

| OS | TokenType | Middleware | Pushサーバ |
|:------------------:|:------------------:|:------------------:|:------------------:|
|iOS             | DeviceToken | Gaurun | APNs
|Android           | registrationToken | Gaurun | FCM

Gaurunとは、mercariが公開しているPush通知向けのGo製のサーバで、APNｓとFCMに対して共通のエントリーポイントを用意してくれて、更にはBulk送信にも対応してくれるというソフトウェアです。
https://github.com/mercari/gaurun
Togetterでは、Gaurunを採用することで、Push通知関連のコードを簡素に記述することができていました。

その後、Android用に提供されていたFCM（FirebaseCloudMessaging）が、AppleのAPNsへのPush通知にも対応し、クロスプラットフォーム向けのメッセージングサービスとして進化したため、上記の構成を下記のように変更できることがわかりました。

2018年6月から〜

| OS | TokenType | Middleware | Pushサーバ |
|:------------------:|:------------------:|:------------------:|:------------------:|
|iOS             | registrationToken | FCM | APNs
|Android           | registrationToken | FCM | FCM

Gaurunに置き換える形で、FCMを採用することで、Tokenレベルで処理を共通化し、且つ、Gaurun用のサーバを破棄することができ、運用コストも削減できます（FCMは現状無料）。

ここで、問題になるのが、APNs向けに発行されているDeviceTokenを利用して、そのままPush通知を送信可能かどうかという点です。FCMには、APNs用に発行している証明書をアップロードするため、これまで発行されたDeviceTokenをそのまま使える可能性が予想されました。

DeviceTokenの移行方法を調べている中でbatchImportによりAPNsのトークンから、registrationTokenを再発行することができることがわかりました。

https://developers.google.com/instance-id/reference/server#create_registration_tokens_for_apns_tokens

このAPIをテストしたところ、DeviceTokenとregistrationTokenは１対１で紐づくことが判明したので、移行に際して複数のregistrationTokenを発行してしまうなどの不具合を心配する必要がないので比較的シンプルに移行作業を考えることができました。

## その１：発行済のDeviceTokenの移行
batchImportは１回に１００件のDeviceTokenを置換可能なため、バッチ処理などで１分おきに１００件ずつ処理するなどしてすべてを置換しました。その際、Tokenのタイプをあわせて変更することで、１の場合はGaurunへ、２の場合はFCMを使うという実装ににすることで、移行中でも停止せずにユーザにPush通知を行うことが出来ました。

```php5
        $client = new GuzzleHttp\Client([
            'base_uri' => 'https://iid.googleapis.com',
            'headers' => ['Authorization' => 'key=' . $sever_key],
        ]);

        $json = (object)[
            'application' => $bundle_id,
            'sandbox' => false,
            'apns_tokens' => $tokens,
        ];

        $respose = $client->post('/iid/v1:batchImport', [
            'json' => $json,
        ]);
```
## その２：旧アプリが発行するDeviceTokenの扱い
すでにAPNs用に使っている証明書を再利用可能なので、旧アプリに関してもregistrationTokenとFCMを使ってPush通知はそのまま可能です。
ただし、FCMベースに更新していない旧アプリは、DeviceTokenを発行してしまうので、そのままでは逆にFCM経由でPush通知できなくなってしまいます。そのため、Togetterでは旧アプリが発行するDeviceTokenは、登録と同時に上記のbatchImportのAPIを利用してregistrationTokenに変換する処理を行っています。
旧アプリを利用停止にしない限り、この処理は引き続き必要になると考えられます。

## その3：APNs関連のコードとGaurunの破棄

ここまでを本番にリリースして、全てのDeviceTokenが変換されていることが確認された段階で、あとはAPNs用のコードをひたすらDeleteしていきます。Gaurun用のインスタンスにも感謝の気持ちを込めつつTerminateしましょう。

## まとめ

batchImportを使うことで、容易にPush通知の仕組みをFCMにすべて移行することができました。batchImportに関する説明がFCMのドキュメントに少ししか書いていないので、見逃している人も多いかもしれませんが、これを使うことでサーバやアプリを動作させたまま、Push通知の基盤を移行し、構成をシンプルにしつつ、管理コストや開発工数の削減ができました。
FCMはバッチ処理やABテスト、既読管理なども可能なので、まだ使えていないアプリ開発者の方は移行を検討してみると良いのではないでしょうか？

トゥギャッターでは、新しい仕組みを積極的に取り込んでいきたいエンジニアを募集しています。
https://www.wantedly.com/projects/188299

文責：@yositosi