文脈
現在のWeb開発現場では
が使われるようになった。
これを、Webアプリケーション開発者はビジネスロジックの開発のみに集中しそれ以外のめんどくさいことは各クラウドサービスに外出しする流れと総括できるだろう。
(WebアプリにはAndroidアプリやiPhoneアプリも含む)
僕はいま、Webアプリにおける通知もこの仲間に入っていくであろうと予測している。
そのことについて書く
通知とは
ここでは以下のものを通知と呼ぶ。
まず、ServiceWorker、iOSのAPNS、AndroidのFCSを使ったプラットフォームのネイティブのサーバーPush型ポップアップ通知機能が挙げられる。
また、Webサービスにありがちなベルマークの右上に未読件数が赤丸で表示されるだけのものも通知に含むことにする。
この場合、そのページにアクセスしたときだけ最新の件数を取得するものと、サーバー側でイベントがあればWebSocket等でベルマークをRefreshする機能を持つもののどちらも通知に含む。
メールやLineを使って通知するものも含む。
通知も内容によって色々あるが、主にSNSにおける「いいねされました」「コメントがつきました」のような内容のものの話をする。
今のお気持ち:通知はめっちゃ難しい
通知の機能を作るのはめちゃくちゃ難しい。
通知のプロトコルやSDKの話ではなく、通知機能を入れるとコードが汚れるという話。
僕はアーキテクチャを整頓するのが得意で、自分が作っている情報システムはコード量が増えれば増えるほど新規機能追加コストが下がっていく傾向がある。
しかし、通知周りはどんどん脳みそにかかるコストが高まっている気がする。
なぜ難しいのか
発生源がたくさんある
Twitterであれば
- フォローされた
- いいねされた
- RTされた
- リプされた
- 引用Tweetされた
- その他広告っぽいもの
などがごった煮になっている。それがコードの追いにくさにつながっていると思う。
また、SQLを含む多くのコンピューター言語は代数的データ型を持たない。
よって、正確なif文を書くのがちょっと難しいケースがある。
未読既読管理
これはDBに大量の変数を持たなければいけないことを意味する。
また、中には既読の中に「画面に表示された」「タップして詳細を見た」等の複数のタイプを持つものもある。
RESTFulとの親和性
場合によってはGET メソッドに副作用を持たせなければいけないかもしれない。
グルーピング
Twitterであれば一つのTweetに二人がいいねしてくれたとき、その通知が1行にまとめられる。
ユーザーにとっては便利だが作る側からすると地味にめんどくさい機能である。
キャンセラブルである
Twitterであれば、自分のTweetに誰かがいいねしてくれて、その後にTweetを消した場合、いいねに対する通知を消さなければいけない。
毎度、DB上にどんな通知が残ってるか考えるのが辛い。
JOINした状態で保存できない
通知はその機能の使われ方の性質上、できれば非正規化しておきたい。
例えばいいねの通知なら、いいねしてくれた人のユーザー名は通知のレコードと一緒に保存しておいたほうが負荷が少ないだろう。
しかし、それをしてしまうといいねした後に名前を変えた場合どうするのかを考えなければいけない。
間違いの影響がでかい
サーバーサイドからPushして画面に表示するタイプの通知の場合、間違って同じ通知を二回送ってしまったらユーザーに嫌われる。
また、間違った内容の通知を送ってしまったら取り返しがつかないケースが多い。
データ量と更新頻度が高い
これは、何か変更があったら関係ありそうなデータをDBをから全部落としてきてキャッシュファイルを洗い替えするような手抜きがしにくいということだ。
常に計算量やDBからの転送量を意識してコードを書かないといけない。
外出しされる部品の特徴
ここまで通知を実装することの難しさを挙げた。ここから通知をクラウドサービスが巻き取ることの合理性の話をする。
上に挙げたクラウドサービスに巻き取られるパーツにはこのような要素があると思う
- 外出してもしなくてもビジネスロジックが変わることがない
- スケールしてくれると嬉しい
- APIや外から見た振る舞いは単純だが、内部に複雑な状態を持つ
- 運用が難しい
- だいたいどの会社も同じようなことをしてる
こう見ると通知 as a Serviceはまさにこれに適していると思う。
どのようなAPIを持つのか
おそらくサーバーサイドアプリケーションからHTTPで問い合わせるようなものになるだろう。
もちろん通知サービスが独自のドメインを持ちクライアントと直接やり取りするような実装も可能である。
事前にYAML等で、このシステムがどんな通知機能を持つのかをアップロードしておくと良さそうだ。
一種の腐敗防止層になってくれそう。
YAMLよりもSwiftのようなコンピューター言語で定義するのもいいアイディアだと思う。
type aliasと代数的データ型を持つ言語なら正確かつ簡単に型付けできていい。
アプリケーションサーバーから通知サービスへ構造体をPostすることで通知を作成する。
このとき、構造体の型と各propertyの値の組み合わせがサービス全体で一意になるようにする。
そうすると、既読管理と重複Push防止ができる。
また、propertyの型をType Aliasとするとリレーションが表現できるので、キャンセルや非正規化する時に間違い防止ができる。
つまり
struct Like {
let from:UserID
let to:UserID
let tweetID:TweetID
}
という構造体にしておけば、関連のあるユーザーかTweetが消されたらその通知は消していいということが型で判断できる。
同様に、ユーザー名が変更されたときも、アプリケーションサーバーから通知サービスにそのユーザー構造体を送れば自動で再JOINしてくれるようにできる。