2歩戻ったら2.5歩進みたい

関東で働くweb developerのブログ

新しいLinuxの教科書を読んだ

読みました。EC2とかDockerでなんとなくLinux触ってるのが気持ち悪かったので、何が分からないのかわかるようになるために読んでみた。この本が対象とするのはLinuxを触ったこともないような、あるいはこれから初めて触るような人に向けた内容だったので、前半は割と知ってる内容が多かった(ファイル操作とかviとか)。後半はパーミッションやプロセス、標準入出力やシェルスクリプトなど、自分が何となく知ってるけどあんまり意識して触ってこなかったような概念についての説明だった。

全体通して非常に文章が分かりやすかったので割とスラスラ読めた。ただまぁsedとかawkとかのテキスト処理やシェルスクリプトは正直苦手意識がある。多分サーバー構築の自動化なんかでシュッと書けると便利なんだろうなーぐらいの感覚なので今ここにコミットしなくてもいいかなーと言う感じ。

とりあえずこの本を読んで「Linuxが何か知っている」ぐらいのレベルにはなったのではないかなーと思う。これで例えばwebアプリケーションの運用がやれるかというとそんなことはないので流石にもうちょっと詳しい本を読むなどしたい。

雑に調べたらLinuxの認定試験のLinucのレベル1が割とちょうど良さそうなのでやってみてもいいかも?と思っている。

linuc.org

章ごとの感想

  • Chapter 01 Linuxを使ってみよう
  • Chapter 02 シェルって何だろう?
    • カーネルとシェルの関係について、など。理解がふわふわしてたので良かった。
  • Chapter 03 シェルの便利な機能
  • Chapter 04 ファイルとディレクト
  • Chapter 05 ファイル操作の基本
  • Chapter 06 探す、調べる
  • Chapter 07 テキストエディタ
  • Chapter 08 bashの設定
    • .bashrcと.bash_profileと/etc/profileの関係についてなど
  • Chapter 09 ファイルパーミッション、スーパーユーザ
  • Chapter 10 プロセスとジョブ
  • Chapter 11 標準入出力とパイプライン
    • ><2>&1
  • Chapter 12 テキスト処理
    • sort, uniq, wc, cutなど
  • Chapter 13 正規表現
  • Chapter 14 高度なテキスト処理
    • sedawk。ムズすぎワロタ
  • Chapter 15 シェルスクリプトを書こう
    • #!/bin/bashがおまじないでなくなった
  • Chapter 16 シェルスクリプトの基礎知識
  • Chapter 17 シェルスクリプトを活用しよう
    • この辺を読んで雑なテキスト処理とかターミナルから実行する運用系のコマンドはシェルスクリプトにして見たりすることが出来た
  • Chapter 18 アーカイブと圧縮
    • tarってアーカイブだけで圧縮じゃないんですね。知らなかった。
  • Chapter 19 バージョン管理システム
  • Chapter 20 ソフトウェアパッケージ

2021年 ふりかえり

月別ざっくり振り返り

やったこと
1月 個人開発アプリのリリースに向けて頑張る
2月 個人開発アプリをリリースする
3月 個人開発アプリの運用・改善する①
4月 個人開発アプリの運用・改善する②
5月 個人開発で燃え尽きる①
6月 ゲームしたり読書したり引越し準備したり(個人開発で燃え尽きる②)
7月 ゲームしたり読書したり引越し準備(個人開発で燃え尽きる③)
8月 引っ越し
9月 家を整える
10月 スマブラと読書
11月 転職活動
12月 転職活動

個人開発

今年の前半は個人開発に注力していました。2020年の9月ぐらいから取り組んでいたので足掛け半年ぐらい開発をしていたことになります。

zenn.dev

リリースまでは朝早起きして1時間コードを書き、仕事を定時で終わらせたらご飯を食べて寝るまでコードを書くような生活を続けていました。この頃はモチベーションに満ち満ちており、個人開発でアプリを出して小銭を稼ぐぐらいまでは行ってやるぞ、と思っていました。

無事2月にリリースにこぎつけたのですが、残念なことにリリース後のトラフィックはほぼ全くの無風でした。もちろんTwitterで潜在ユーザーに直接宣伝してみたり、既存のユーザーにヒアリングしてみたり、改善はやったつもりでしたがそれ以上にモチベーションが保ちませんでした。

今振り返ってみるとニーズの捉え方が甘く、結局はユーザー(自分含め)が本当に欲しい物をわかっていなかったことが成功できなかった原因だったと思います。まぁそれはそれとして、自分が全力で出したアウトプットのクオリティの低さに耐えられなかったのもあり、燃え尽き症候群のようになってしまいました。結局そのままアプリは放置しています。どこかのタイミングでモチベーションが戻ってきたらまたやってもいいのですが、しばらくは別のことに時間を使おうかなと思っています。

本とかゲームとか

5~6月は余暇の時間を読書やゲームに向けました。

モンハン。PSP以来だったのでついていけるか不安だったけど、とりあえずソロで全部回れはした。アクションがめっちゃ進化してて楽しかったです。

FE風花雪月。リシテアちゃん可愛いよ。

発売当初にリトルマックでVIPに入って満足してたんだけど、友達が本格的にやり始めたので巻き込まれる形でまたやり始めた。オンライン対戦のレベルが上がりすぎてて怖い。来年はちゃんとVIPに入れるようになりたい...。

展示を見に行く度に歴史を知らないのは勿体ないような気がしていたので読みました。印象派以降の流れが分かったので色んな画家に対して親近感を持てるようになった。嬉しい。

あとこの辺。もうちょっと読んだ気もするけど...。

canisterism.hatenablog.com

canisterism.hatenablog.com

引っ越し

今年の後半に入ってからは引っ越しが大きなトピックでした。今の彼女と同棲することにしたので大田区から川崎に引っ越しました。23区に比べると家賃がバカみたいに安くなりました。

引っ越しを経験するのは人生で3回目ですが何回やっても大変ですね。特に今回は初めての同棲だったので、家具家電をほぼ全部新調するために2人で検討するのが大変でした。必要な家具家電を全部洗い出して暫定の予算を書き込んだ上で必要なタイミングを考えながらいつ買うのかを決めるためにスプレッドシートに作ったり、引っ越しのタスク整理のためにNotionで看板を作ったりしました。ほぼ仕事でしたが普段プログラマーとして働いているのでプライベートでPMっぽい仕草をするのは楽しくもあったので良かったです。

もう語り尽くされてますが、ドラム式洗濯機とロボット掃除機を入れたのは今でも正解だったと思ってます。この辺の話をどこかで書きたい。

f:id:canisterism:20211228153544j:plain
PanasonicのNA-VX700BL

この動画がすごく参考になりました。

【コスパ最強】洗濯乾燥機をスペックで選ぶのは危険!下位機種で良い理由!おすすめが1つだけありました - YouTube

実際に引っ越したのは8月でしたが、9月〜10月まで家具選びを頑張っていた記憶があります。ソファとか棚とかテレビとか。また、自室の環境も引っ越しに伴っていい感じにしたいと思っていたので思い切って電動昇降式机を導入しました。好きな時に立ったり座ったりできるのは最高。

お仕事

仕事に関して、今年はチームで上手くバリューが出せなくてずーっと悩んでいたような気がします。親しくしていたメンバーの方がチームから抜けたり、チームにおける自分の存在意義が上手く見いだせなかったりしてモチベーションがうまく継続できませんでした。

また、Flutterという技術を自分のキャリアにおいてどういう位置づけにするのか?という問いをずっと考えていました。

現職ではFlutterを使ってモバイルアプリエンジニアとして働いていますが、このままモバイルアプリエンジニアとしてキャリアを積んでいくのか?という疑問が今年の中盤からずっと頭の中で渦巻いていました。

Flutter自体は素晴らしいフレームワークですが、2年間触った感想としてネイティブのレイヤーに近くなれば近くなるほどFlutterによるデメリットが浮き彫りになっていくように思います。単純なJSONのやり取りだけであれば良いですが、Webviewやテキストフォーム、カメラなどの機能を使うことを考えるとつらみが多くなってくるのではないでしょうか。(Google謹製のアプリのうちいくつかはFlutterベースに書き換えられているのを見ると単純に僕のスキル不足によるものである可能性も否めないですが)

つまり(?)、今時点の自分のFlutterに対する認識はこんな感じです。

  • クロスプラットフォーム開発のためのフレームワークとしては素晴らしい
  • ただし、ネイティブのAPIに近いことをやろうとすると結局ネイティブのコードを書く必要がある
  • 自分はネイティブのコードが書けない(し、あんまり書くつもりがない)
  • ネイティブのコードが書けないFlutterエンジニアがマッチするのは「(複雑なことはせずに)とりあえず両プラットフォームでモバイルアプリが欲しいプロジェクト」である

なので、Flutterをキャリアの中心に置き続けるには、ネイティブのコードが書けるようになるか、ネイティブのコードが書けなくても問題ないようなアプリを書き続けるかのどちらかになると考えました。自分はモバイルアプリを作り続けたいわけではなく、一人のwebエンジニアとしてプロダクトを出すための力をつけていきたいと思っています。こういった考えから、今のチームでFlutterを書き続けるのはやめて、別の会社でwebアプリケーションを作るエンジニアとして働く決断をしました(書いてみると決断が一足飛びな気もしますが、まぁ書きにくいことも色々あるのでお察しください)。

注:Flutterを書いてる/好きな方やFlutter自体を貶したい訳ではないです。個人のキャリアとしての観点なので悪しからず。

というわけでなし崩し的に転職エントリになってしまいましたが、来年の1月から別の会社でお世話になります。現職には3年とちょっと在籍したので、そろそろ別の会社を見てみたかったのもあります。

次の会社ではおそらくまずはRailsでバックエンド+インフラもやることになる(というかやりたい)と思っています。今までどちらかというとUI寄りの開発が多かったのですが、ゼロイチでサービスを作れるようになりたいので新しい挑戦をしたいと思います。

総括

なんだかまとまりのないエントリになってしまいましたが今年の振り返りはこんな感じです。全体的に集中力が下がってしまって、まとまったインプット/アウトプットが出なくて悔しい感じになってしまいました。いろんな失敗を経験したのでもうちょっとちゃんと振り返りをして次に繋げたい...。

キャリアに関して、正直今でも明確な回答が出てる訳ではないんですが、自分なりに考え抜いた結果出た答えなので、来年は修行だと思ってひたすらコードを書いていきたいと思います。来年の目標もサクッと書けるといいなー。それでは良いお年を!

f:id:canisterism:20211228174144j:plain
武蔵小山「てりや」の のどぐろ。なんとなくめでたそうなので。

DNSがよくわかる教科書 を読んだ

動機

最近の中長期目標が「webエンジニアとしての基礎を作る」なので今まで避けてきたアプリケーション以外の技術に触れてみる一環で読んだ。直前に読んだ本がマスタリングTCP/IPだったのでネットワークつながりで続けて読むことにしたのだけれど結果的に良い判断だったと思う。

canisterism.hatenablog.com

感想

実務でもDNSのレコードを直接操作したり、名前解決のプロセスを辿ってトラブルシュートしたりすることはなかなか機会に恵まれなくてどうにも苦手意識があったのだけれど、少なくとも概観を掴めたのでDNS何もわからん!という状態ではなくなった。

基礎編ではDNSの構成要素や名前解決の仕組み、委任などの説明を交えながら具体的な動作の説明に入っていく。教科書と銘打ってる通りゼロベースで説明してくれてるので自分のように理解があやふやな人にも優しくて助かった。

8章のdig他DNS周辺のCLIコマンドを実際に叩きながらフルリゾルバとして名前解決をしてみるセクションがすごく理解を助けてくれた。digコマンド自体は知ってるけどあんまり叩く機会がないので...(一方で実際に手を動かすパートはここぐらいしか無いので理解を深めるためには別の書籍をあたったほうが良さそう)

読んでてピンとこなかったことは検索しながら読み進めたのだけれど大体jprs.jpやnic.ad.jpのサイトに詳しい解説が載っている。

jprs.jp

www.nic.ad.jp

DNS周辺、いざって時に触れたり分かってたりすると役に立つタイプのものだと思うので、これを読んで苦手意識を消すレベルまで引き上げられて良かった。

マスタリングTCP/IP 入門編を読んだ

webエンジニアになりたての時に「エンジニアにおすすめ!」みたいな記事を読んで買ったんだけど当時何が書いてるのか全く分からなくて積んでしまっていた本。

3年ぐらい経った今、ぼんやりエンジニアとしての基礎が足りないと感じていたところにふと目についたので手にとって読んでみた。

感想

序盤の章ではインターネットの歴史からOSI参照モデルTCP/IPにおける簡単な通信の流れをさらって、以下の章では各層のプロトコルを順番に解説していた。

ネットワーク周りがずっと苦手で避けてきたので前提知識がほぼ無いような状態で挑んだ割に面白く読めたと思う。プロトコルを普段の生活に例えて説明してくれるところが読みやすかった。例えばTCPとIPにおいて目的のホストに辿り着くまでの経路解決が「駅員に目的地だけ伝えたら次はどこに行けばいいか分かる」という説明をされていて分かりやすかった。

自分のようなネットワーク何もわからんマンには序盤の章でざっくりした全体図を示してくれるのはすごく助かった。あと全然関係ないんだけどTCP/IPの標準化までのプロセスは実際に役に立つ仕様を作成するための仕組みという感じがして面白かった。

3章以降は各層のプロトコルに関してもう少し突っ込んだ説明がなされていて、こっちに関してはふんわりとわかった気がする...というのも、読んだ時は理解してるんだけど応用するための知識として引き出すには足りないのでもうちょっと実務で使うなり深めるための学習が要るような気がする。とはいえ一回分かってればあとは引き出すことは出来そうなのでそういう意味で読めてよかった。

本筋とは別の話で面白かったこととして、全体通してプロトコルが発展する時の思想としてひたすら「実践的かどうか」を問いながら進んでいるのが面白いなと思った。プロトコルを先に策定して広めるのではなくて、提案をベースにして実験と議論を行い、主要メンバーが承認すると実装され始め、その上で広く運用されると晴れてStandardになる...というプロセスを踏むので標準として策定されたときには十分に洗練された状態になっている。ただインターネットの偉人たちによってこれだけ標準化までのプロセスを洗練させても、長い時間が経つと想定外の事が起こってるのを見ると事前に起こる問題を予想し切るのは無理なんだなぁ...と思った。

話が逸れたけど、短期的に見ると効果が薄いけど長い目で見たら読んだほうが良いタイプの本をちゃんと読み切れたのはすごく良かった。このベースがあると今後の実務レベルの知識が入ってきやすくなると良いな〜〜〜〜〜。終わり。

プッシュ通知を作る前に知りたかった事のメモ(Firebase Cloud Messaging + AWS SQS & Lambdaでプッシュ通知を作った)

この記事について

プッシュ通知の基盤を作る機会があったのですが、前提となる知識や知見が足りなくて調査や検証に時間がかかったので、設計する前に知りたかったことや作り始めてから気づいたことなどをざざっとまとめました。 これからプッシュ通知を作る人に向けて参考になれば幸いです。

前提

具体的な説明に入る前に、前提となる要素と今回の開発で求められた要件を並べます。

対象のサービスやアーキテクチャについて

webアプリとモバイルアプリが共存しているサービスで会員数は100万人程度、webアプリケーションはRails製です。

このRailsのアプリケーションはwebアプリとして振る舞う以外にもモバイルアプリのAPIサーバーとしても振る舞っています(厳密にはサーバーが別れてますがコードベースは同じです)

モバイルアプリはiOS, AndroidともにFlutterで書かれていて、Railsのアプリケーションをリソース(API)サーバーとして見ています。

配信の要件

配信の要件はだいたい以下のような感じで、いわゆるモバイルアプリのプッシュ通知が出来ることです。

  • アプリの全ユーザーに対してアドホックにプッシュ通知が出来る
  • アプリの任意のユーザーに対してアドホックにプッシュ通知が出来る
  • 特定の条件に合致したユーザーに対してバッチでプッシュ通知が出来る
    • クーポンが期限切れになる前にプッシュ通知でリマインドする、みたいなやつです
  • アプリケーションの特定のイベントをトリガーとしてプッシュ通知が出来る

分析の要件

加えて分析やマーケティングを担当するチームが居るので最低限以下のような要件が求められます。

  • 送信したプッシュ通知の一覧が確認できる
    • 送信した通知に関して開封された数が確認できる
  • 任意のユーザーに送信された通知の一覧が確認できる
  • 任意のユーザーの開封後の行動をトラッキングできる

技術スタックについて

今回の開発では上記の機能要件に加えて、非機能要件として頭が痛くならない程度の予算で運用できることや配信サーバーの面倒を見ないでも運用できることを念頭に置きました。

既存のシステムとの整合性も考えた結果、今回はFirebase Cloud Messaging + AWS SQS & Lambdaで構成することにしました。

それぞれの技術について&選定の理由を説明します。

Firebase Cloud Messaging(FCM)

firebase.google.com

まずプッシュ通知を実現するためのサービスを選びます。アプリがFlutterでできている時点でほぼすべてのプッシュ通知サービスがSDKを用意していないという理由で選べませんでした。(こういう検索で出てくるサービスです↓)

www.google.com

ReproやKARTEはFlutterのSDKが用意されてました(すごい)が、現時点で料金面が折り合わないことやマーケティングツール以外からの起因で配信することを考えると初手でそこを選びに行けませんでした。(とりあえず自前で一通り作ってからマーケティングツールも入れる...みたいなのはできそうだと思ってるけどぶっちゃけあのへんのツール群をよく分かっていない)

という訳でFirebase Cloud Messagingが選ばれました。FlutterとFirebaseはすごく相性が良くてSDKが整備されているので正直モバイルアプリがFlutterな時点でFCM一択な感じはありました。 単純に他と比較しても料金が1円もかからないことやBigQueryとのデータ連携、ドキュメントの豊富さなどを鑑みても良いツールだと思います。

補足:FCMの配信の方式について

FCMの配信の方式は大きく分けると2つあります。

トークン指定配信

前提として、プッシュ通知を行うためにはプッシュ通知のトークンが必要になります(FCMだとregistraion tokenと言います)。これはプッシュ通知を許可した端末から払い出されるトークンで、これを指定してAPIを叩くとトークンを払い出した端末に対してプッシュ通知を送ることができます。

トークン指定配信(と勝手に呼んでるけどドキュメント上は特定のデバイスにメッセージを送信するという表現のみ)は直接配信先としてこのトークンの文字列を指定する配信です。

firebase.google.com

トピック配信

トークン指定配信は通知対象を直接指定するので分かりやすいですが、一回のAPIコールで指定できるのは500トークンまでです。仮に全ユーザーに対して配信するケースが存在した場合、(会員数にも寄りますが)配信に時間がかかったりサーバーから通知している場合はマシンリソースを逼迫させる可能性があります。

この弱みを解決してくれるのがトピック配信です。トピックというのはトークンを紐付けることの出来る対象で、トピックに対してメッセージを送ると事前に紐付けたトークンを持つ端末に対して通知を送ることが出来るというものです(トピックへの紐付けはクライアント/サーバーのどちらからでもできます)。

つまり、事前に配信したい対象が決まっているなら紐付けさえやっておけば、トピックに対する紐付け上限はない(はず)なので1リクエストで多くの端末に対して配信することができます。

ただし配信の遅延が最大で24時間(ドキュメント曰く)だそうなのでその辺りは理解して利用するのが良いと思います(恐らく紐付けたトークンの件数にも左右されると思いますが未確認です。手元で100個以下の紐付けならほぼ一瞬で配信されました)。

また、トピックを使う場合はトークンのライフサイクルと合わせた更新の設計が重要になります。例えば男性や女性と言ったセグメントでトピックを切った場合はトピックに紐付いたトークンを操作するのはトークン自体がrevokeされたり再生成されたりするタイミングだけですが、年齢のようなセグメントでトピックを生成した場合だとトークン自体のライフサイクルに加えて年一回定期的に変更するバッチを作る必要があります。

このため、私は初期フェーズでは全配信のためだけにトピックを使用することとし、配信の単位が固まってきたタイミングで再度トピックの設計を行うことにしました。

AWS SQS + Lambda

次にプッシュ通知のAPIを叩く君が必要になるのでそこの技術スタックを決める必要があります。

具体的にはアプリケーションの任意のイベントをトリガーにして対象のユーザーにプッシュ通知のAPIを叩いたり、アドホックなお知らせの配信をするために任意のタイミングで通知のAPIを叩いたりするコンポーネントです。

ざっくり言うとアプリケーションサーバーから直接FCMのAPIを叩く or 別の層を挟む のどっちにするのか、という議論です。現時点ではモバイルアプリの会員数はあまり多くない(~10万)のでマシンリソースのことは考えなくて良いのですが引き返しにくい判断(構成を変えたくなった時の難易度が高い)なので、サーバーを持たずにできるだけシステム間の結合が疎になるようにSQSを挟んでLambdaからAPIを叩くことにしました。

なお、Lambdaはnode.js + typescriptで作っているのでFCMのadmin SDKが使えます。(Railsから叩きたくなかった理由としてFCMのRubySDKが存在しなかったから、というのもあります)

システム全体図

という訳で改めて作ったシステム全体の図を示します。

f:id:canisterism:20211002010022p:plain

これを踏まえて、プッシュ通知が実際に行われるまでの大まかな流れは以下のような感じです。

トークン登録編

  1. アプリの起動時にトークンが払い出される
  2. アプリケーションサーバーにAPI経由でトークンがDBに登録される
  3. トークンを全体配信用のトピックに紐付けるためにSQSにキューイングする
  4. SQSからLambdaがキューを取得してFCMのトピック紐付けのAPIを叩く

全体配信編

  1. 配信用SQSに通知のデータをキューイングする
  2. SQSからLambdaがキューを取得してFCMのトピック配信のAPIを叩く
  3. ユーザーに通知が届く

ユーザー指定配信編

  1. DBから配信対象のユーザーを抽出し、ユーザーが持つトークンを取得する
  2. 配信用SQSに通知のデータをキューイングする
  3. SQSからLambdaがキューを取得してFCMのトークン配信のAPIを叩く
  4. ユーザーに通知が届く

設計上の注意点や気をつけたこと

上記の流れはそこまで複雑なことをやってないのですが、僕が設計する時にもっと早く知りたかったことや気をつけたことなどをざっと書いてきたいと思います。

アプリ側のトークン払い出し周辺の知識

先程も説明したように、プッシュ通知がなされるためには最初にユーザーの端末で通知トークンを払い出す必要があります。最初は「まぁダイアログ出して終わりやろ」と思ってたのですが意外に複雑でした。だいたいはFirebaseのドキュメントかFlutterならFlutterFireとfirebase_messaging | Flutter Packageに書いてありますのでよく読みましょう。(下に書いてるのはFlutterのAPI特有だったりするかもしれないので差っ引いて見てください)

  • 通知を許可するかユーザーに聞くダイアログが出るのはiOSのみ。Androidは何も出ずに許可されたのと同じステータスになる。
    • 1回でもダイアログを出すと2回目は出ない(ダイアログを出した後強制的にアプリを終了すれば2回目が出るかも?未検証です)。
      • そのため、「公式のダイアログを出す前に通知のメリットを説明する独自のダイアログを出す」みたいなことをしているアプリは多いです。仕様検討の際には気をつけたいですね。
  • 通知の許可ステータスはauthorized, denied, provisional, notDeterminedの4つ。
  • Flutterのfirebase_messagingで単純なプッシュ通知を行うだけなら両OSともgetToken()だけでよい(getAPNSToken()は使わないでOK)。
  • FCM via APNs Integration | FlutterFireに記載があるAPNのkeyはbundle IDに紐付いてないので環境別に分けなくてもよい(分けても良いと思いますが)

トークンのモデリングやライフサイクルにまつわる話

  • トークンは端末に対して払い出すので、アプリ上のユーザーと対応させたいなら工夫が必要
    • 例えばユーザーAがログインしている状態でトークン(aとする)を払い出した後にログアウトしてユーザーBでログインし直した時にaのトークンをどう扱うか?という話。
    • Twitterのように複数ユーザーの同時ログインを前提としているならそのまま通知して良いと思いますが、同時に1ユーザーのみログインを前提としている場合はログアウトしたらトークンを無効とする処理を入れるなどしないとユーザーAへの通知がユーザーBに行ってしまうので考慮する必要があります。ややこしいですね!
  • トークンはDB上で永続化するべし
    • 当たり前過ぎて書くか迷ったのですが作る前はこの辺も迷ったので書いておきます。
    • アプリ側でトークンが払い出されたらDBに永続化しましょう。でないとサーバー側のイベント起因で配信ができないので...
    • この時、ユーザーとトークンの関係は1:1ではなくて1:nであることに気をつけましょう。単純に複数台端末を持ってる可能性がありますからね。

開封率などデータ分析の話

この辺に書きました。 canisterism.hatenablog.com

トピックに纏わる話

  • 記事執筆時点でFCMのトピックは削除することが出来ません。一回作ったトピックを削除するためには紐付けたトークンを全部unsubscribeする必要があります。
  • トピックの一覧やトピックに紐付けたトークンを取得する方法がないため、管理したい場合は自前のDBで紐付けたトークンとトピックを管理する必要があります。

stackoverflow.com

アプリの通知のハンドリングの話

  • 後方互換性には気を使ったほうが良い
    • これはどっちかというとプッシュ通知と言うかアプリのバージョンアップで気をつけることですが、通知を受け取ることの出来るバージョンは平等に通知を受け取れることに留意して最初のバージョンをリリースするべきでしょう。つまり、v1.0でプッシュ通知機能をリリースした後、v1.1で新しい画面が追加された&その画面へ遷移するプッシュ通知も送られるようになった、みたいなことが起こるとv1.1で追加された画面への遷移を含む通知がv1.0にも届いてしまいます。ちゃんとフォールバック先を用意するなどの工夫が必要なので気をつけましょう。

まとめ

なんだかまとめきれたのか怪しいですが、僕が設計&実装する前に知りたかったことを詰め込んでみました。プッシュ通知したい時はだいたいどれぐらいのことを設計のスコープとして見込めばよいのかの肌感が伝わってれば嬉しいです。

他にもいろいろ考えた気がしますがちょっと思い出せないので思い出したら追記します。

参考リンク

Firebase Cloud Messaging | FlutterFire

FCMを使ったWEARプッシュ通知基盤リプレイス - ZOZO TECH BLOG

大規模プッシュ通知基盤大解剖 - @IT

Firebase Cloud Messagingで開封周りの分析に必要なデータを整理する

3行で

  • FCMでプッシュ通知の開封率を計算するにはAnalyticsの自動収集イベントが使える
  • 特定のユーザーに対して配信された通知の一覧を取得するにはinstance_idをキーとすれば良さそう
  • instance_idは端末に対してユニークではないので注意されたし

やりたいこと

Firebase Cloud Messagingで作ったプッシュ通知からBigQueryに出力されるデータで「誰にどの通知を送ったのか、どれが開封されたのか」を取って行動分析したい。

もう少し具体的に言うと、以下のようなことをしたい。

  • 一斉に送ったプッシュ通知の開封率を計算する
  • 特定のユーザーに送った通知の一覧を取得する

データの取り方を考える

まず開封率の事を考える。開封率は開封された通知の数 / 送った通知の総数で求められるのでそれぞれをどうやったら取れるか考える。

送信済みのプッシュ通知の情報

まず、FirebaseとBigQueryを連携するとFCMの送信データが自動でBigQueryに連携されるので、送信された全通知のデータはここから取れる。

firebase.google.com

開封した記録

次に「どのユーザーがどの通知を開封したか」というデータを取得する。

iOS, AndroidともにFirebase Analyticsが入ってれば自動でイベントが収集されるんだけど、この中にnotification-openというイベントがある。

これはプッシュ通知の開封を記録したイベントで、パラメータにanalytics_labelとユーザーIDが自動で渡される。

support.google.com

analytics_labelは通知の送信時に送信者側で任意の文字列を設定できるので、「20210924_キャッシュバックキャンペーン通知」みたいなのを設定しておけば開かれた通知の種類が判別できる。

analytics_labelをキーにすれば開封された通知の数 / 送った通知の総数が計算できるので、開封率の計算に必要なデータはこの時点で揃った。

特定のユーザーに対して配信された通知の一覧を取得する

開封率に関しては計算できたんだけど、「特定のユーザーに対して配信された通知の一覧」はもうちょっと手がかかる。

BigQueryに連携された送信済みデータにはユーザーIDが紐付いていないので特定のユーザーに対して送った通知の絞り込みが出来ない。

送信された通知とユーザーIDの紐付け

先程言ったように、開封された通知に関してはユーザーIDが渡されるので紐付けが出来る。ただし、「送信されたけど開封されなかった」通知の一覧が分からないのが問題。

FCMから連携されてBigQueryに格納されたレコードには送信したプッシュ通知のレコードが並んでいるものの、それぞれの通知に対してユーザーIDや通知トークンが入ってるわけではない(もちろんAPIにユーザーIDを送信する口もない)。

ここで役に立つのがinstance_idである。

instance_id

ドキュメントには以下のようにある。

instance_id: メッセージの送信先のアプリのインスタンス ID(利用可能な場合)

読んだ当初、端末に対して一意なIDかと思ったのだけれどドキュメントを漁るとどうやらそうではない。(↓はAndroidのドキュメント)

firebase.google.com

Firebase Instance ID provides a unique identifier for each app instance and a mechanism to authenticate and authorize actions (example: sending FCM messages).

Firebase Instance IDはアプリのインスタンスに対してユニーク、とある。また、

Instance ID is stable except when:

  • App deletes Instance ID
  • App is restored on a new device
  • User uninstalls/reinstall the app
  • User clears app data

とあるので要するに

×:同じ端末に対して払い出されるIDは毎回同じ ○:5回アプリをインストールし直したりすと異なる5つのIDが払い出される

ということである。

また、instance_idは"${instance_id}:${token_body}"という風にFCMのトークンの前半の文字列として使用されている。

つまり、ユーザーIDとinstance_idが何かしらのDB上で紐付いていれば、BigQueryのデータと組み合わせることでユーザーIDからinstance_idをキーにして特定のユーザーに対して送信された通知の一覧が取得できる。めでたしめでたし。

おわりに

FCMのドキュメントが微妙に散らばっていてこの辺の仕様を理解するのにやや時間を要してしまった。間違ってる記述があったら優しく教えて下さい...🙏

参考

BigQueryに連携されるFCMのデータ定義: firebase.google.com

PopupMenuButtonでいい感じのヒントボタンを実現する

Flutterで「お知らせとかヘルプみたいなのを出したいけど、SnackbarとかDialogほど押し出しすぎるしtooltipだと足りないんだよな〜〜〜〜〜〜」って時にこんな感じのボタンを作ったのでメモ。

f:id:canisterism:20210922215150g:plain

class HelpButton extends StatelessWidget {
  const HelpButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<Text>(
      shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(8))),
      icon: const Icon(Icons.help_outline),
      itemBuilder: (context) => [
        PopupMenuItem<Text>(
          enabled: false,
          child: Text(
            '使い方がわからないかな?ほごしゃのひとに聞いてみよう!',
            style: Theme.of(context).textTheme.caption,
          ),
        )
      ],
    );
  }
}

PopupMenuButton自体は複数のメニューから選ばせる時のUIだけど、PopupMenuItemを1つだけ渡してenabled: falseにしてやるといい感じのヘルプダイアログになってくれる。 api.flutter.dev

ダイアログの位置や折り返しはFlutter側がいい感じにやってくれるので気にしなくていいもの嬉しいですね。

Material Designを眺めながらどれだったら実現できるか頭捻ってたら社のデザイナー氏がmenuで作れないですかね?って言ってくれたのを参考にしたら出来た)