isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

将来サーバーもフロントエンドも一つの言語で書くようになる。

要するに

f(RDB , ユーザーイベント) = UI

序文

僕がWebアプリケーション開発を始めてから6年くらいずっと思っていたことです。
いわゆるIsomorphicの話ではないです。サーバーもフロントエンドも同じ言語で書くと楽だよねというようなことではないです。

本質的な主張

プログラマアセンブラを書く時はレジスタの使い方を指示します。C言語を書くときはコンパイラが自動的に割り当ててくれます。一般的に、普通のプログラマーのスキルと比べれば、コンパイラに任せたほうがパフォーマンスが高いとされています。
同じように、サーバーサードプログラムとフロントエンドプログラムを分けず一つの言語で一つのアプリケーションを書き、機械的にサーバーとフロントの処理の分担を導出したほうが良いのではないかという主張です。

凄く極端な例

フロントエンドのソースコードの中に直接SQLが書かれていたらビビります。普通に考えてとんでもない脆弱性です。しかし、もしそのSQLが動的に実行されるのではなく、webpackの中でprepared statement化されて、実際のRPCはSQL通し番号と引数をやり取りしているだけであれば脆弱性ではないです。
このように、フロントエンドのコードを正にしてサーバーサイドの処理を生成することができます。
突き詰めれば、「最終的に画面に何を表示したいのか」を厳密に宣言出来るなら書式は何でもよく、そこから実装を導出できるはずです。

職業柄思うこと

そんなの無理だろor無意味だろと思うかもしれませんが、僕はこれを毎日頭の中でやっていて、大変つらいと思っているので、なんとか自動化してほしいというお気持ちはご理解いただきたいです。

自分はいつもDB〜サーバーサイド〜フロントエンドを一人で設計・開発・運用していることが多いです。
DB〜サーバーの間にキャッシュ用のKVSが挟まることが多いです。
また、サーバー〜フロントエンドの間にFirebaseを挟むことも多いです。

最初に画面に何を表示したいか考えます。次に、DBにどうやってデータが入っているか考えます。そこから、その間をつなぐ実装を逆算していきます。

考えることは

  • この画面に必要十分なデータはRDBのどの範囲か
    • ≒サーバー→フロントエンドの通信を最小化するには
    • IndexedDBに入れて使いまわすか
  • この画面を表示する度にRDBにクエリを発行して大丈夫か
    • ダメならどんなキャッシュを用意しなければいけないのか
    • そのキャッシュを更新する条件は何か
  • WebSocketなどを使って更新を通知する必要があるか

などです。

ここで僕は2つのことを思います。一つは、計画立案自体を自動化出来るのではないかということです。もう一つは、この計画を元にソースコードを生成できるはずではないかということです。

上の「考えていること」は関数の最適化と言えます。
関数の最適化とはこういうことを言っています。まず最も素朴な関数を書きます。この関数はパフォーマンスがとても悪いです。しかし簡単に書けます。この関数を書き損じることはほぼないと考えます。その関数の挙動を変えないように式変形していきます。それによってパフォーマンスを向上させていきます。これを関数の最適化と呼ぶことにします。
ここでは、まず画面をRDB全体を引数に画面を返す関数を想像し、それを通信量とクエリ効率の観点から脳内変形してパフォーマンスを向上させています。
僕は、こういうことこそコンピュータに任せるべきだと考えています。自動化により計画立案ミスを防ぐこともがきます。また、人間は複雑な最適化を嫌がりますが、コンピュータなら嫌がらずにパフォーマンスのいいキャッシュ戦略を立案してくれると思います。実際、僕もキャッシュの種類を増やすことをとても嫌がります。管理が煩雑になり、その後の開発効率を落とすからです。しかし、コンピュータに立案させておけば、ビジネスロジックを変更する度に再立案させればいいので、気が楽です。また、自分のプロダクトでは効率のいい差分更新ではなく効率の悪い洗い替えをしているところがたくさんあります。めんどくさいからです。コンピュータならちゃんとやってくれると思います。

次に、コードの自動生成です。
自分はサーバーサイドもフロントエンドも書き慣れているので、上記の計画が立ったらその後に書くコードを隅々まで想像できます。僕に出来るんだから他の人にはもっといいコードが想像できるはずです。そうであれば、その計画→コードの変換を定式化しておくことで、コードの自動生成ができると思います。

統一言語はどんなものか

まず、SQLDDLを書くということは無くなりません。
また、統一言語はVDOM的なテンプレートエンジンを持っています。そのテンプレートを書いた時点であるページの型が定まります。
統一言語はSQLのテーブルの型とページの型の2つを読み込みます。
そして、画面ごとにDB上の全データを該当ページのVDOMに変換する純粋関数を書きます。どんなにパフォーマンスが悪くてもいいので最も簡単でわかりやすい関数を書きます。
この言語で書いたWebアプリケーション定義をコンパイルすると、フロントエンドで実行されるJSとサーバーサイドで実行されるバイナリが出力されます。そのコードは、サーバー↔フロントエンドの通信の最適化やKVSを使ったクエリ結果のキャッシュなどが合理的に実装されています。

ユーザーイベントの扱いも重要です。
ユーザーイベントもReact的なものと同じ方針で扱います。
ユーザーイベントを値としてStoreに保存しておき、その値も上の関数の引数になります。実行時にその処理をサーバーサイドでやるのか、フロントエンドでやるのか、楽観的UIは必要か、などはコンパイラが考えてくれます。

よく表を絞り込みできるUIがあります。絞り込み条件をサーバーに送ってサーバーが絞り込みをしたほうがいいのか、該当しそうなデータを全部フロントエンドに送ってフロントエンドで絞り込みをしたほうがいいのか、悩むことが多いと思います。総合的にはサーバーサイドでやったほうがいいが、フロントエンドでやったほうがめんどくさくないからです。
でも、この仕組みなら、どちらを選んでもほぼ同じコストで実現できるので、常に合理的な方を選ぶことができます。

アクセス権限はどうするの?

あんまり考えていません。確かに、どこまでの処理をサーバー側でやってどこからフロントエンドでやるかをコンピュータに決めさせているので、予想外に秘密の情報をフロントエンドに送ってしまう可能性はあります。適当にアノテーションを入れたりすると思います。

RDBMSを作ろうと思う

Cloud Datastoreのラッパーを作りたい

自分は普段GAEのCloud DatastoreというKVSを使います。
KVSは多くの場合とても速いですが、RDBほどやりたいことを直接実現できない場合があります。
特に集計系の操作が苦手です。
例えば画面に「月ごとの売上」を表示するのは一筋縄では行かないです。

個別のケースを最適化することは可能です。
上記の例であれば「月ごとの売上テーブル(Datastore用語ではKind)」のようなものを作って、売上データを保存する時に同時に月ごとの売上テーブルに加算したりします。
しかし、これらの処理をいちいち作るのは大変であり、アルゴリズムの使い回しができず、コードの保守も大変です。
そこで、Cloud Datastoreのラッパーフレームワークを作りたいなと思っていました。
KVSでありがちな困りパターンとその解決戦略を全て実装しておき、各アプリケーションは戦略を選ぶだけで自動的にいい感じにしてくれるものを作りたいなと思っていました。

RDBを作ってみたい

普通に考えて無理では?

話は変わります。PostgreSQLMySQLのような立派なRDBを作ってみたいと思いました。理由は特にないですが、プログラマーに生まれたからにはCPUとOSと言語とRDBを作ってみたくなるものではないかと思います。「俺の考えた最強のRDB」を作りたいのです。
自分のRDBに入れたい超便利な機能のアイディアもあります。例えば僕はFirebaseが大好きなのでFirebaseと連携できる機能を作りたいです。
しかし、RDBというのはとてもむずかしいものだと思いました。
RDBチューリング賞を何人も出している一大学問ジャンルであります。データベースの理論に精通していなければいけないと思います。
また、実装面でも

に精通していないと既存のRDBより出来のいいものを作るのは不可能だと思っていました。

用途を限れば出来るかも

しかし、クラウドで使う前提のRDBを作るだけなら簡単なのではないかと思いました。
まず、特定のOSで動けばいいので、互換性のためのコードが要りません。
GCPAWSにはCloud DatastoreやDynamoDBといったシンプルなKVSが用意されています。
これにRDBの皮を被せればいいのではないかと思いました。
これらのKVSはすでに

といった必要な機能を全て備えています。
また、GCPAWSにはそれぞれGAEとLambdaといったサーバーレスアプリケーション実行環境を用意しています。プロセスが落ちた場合に備えたりラウンドロビンを実装したりする必要がありません。
後はKVS上に参照整合制約やドメイン制約(いわゆる型)を実装するだけだと思います。

作る!

以上の2つの話から「GAE上で動きCloud DatastoreにRDBの皮をかぶせるラッパーフレームワークを作ればいい」という結論に達しました。

〇〇ではダメなのか

クラウドRDBホスティングサービスではダメなのか

例えばGCPのCloud SQLAWSのRDSがあります。
これらは、プログラマがこれまでと同じようにPostgreSQLMySQLを使い続けたいと言ったから始まったマネージドサービスです。
そもそも、PostgreSQLMySQLができたときには、今のようなクラウドコンピューティングはありませんでした。ということはクラウドで動かすなら不要な機能はいっぱいあると思います。
想像ですが、これらのホスティングサービス内で動いているRDBは、オリジナルの実装とは全然違うはずです。PostgreSQLプロトコルを喋りPostgreSQLと同じ出力をする全然別の何かが動いているはずです。
ところが、自分はRDBの機能の殆どを使ったことがないです。SQLの文法もほとんど使ったことがないです。もちろん、プログラマとしてずっとRDBを使ってきました。それでも、全機能の内使ったことがある機能は5%くらいじゃないかなと思います。
これはとても非効率なことです。プログラマPostgreSQLがほしいと言ったから、クラウド事業者はPostgreSQLと全く同じ動きをする全然別のものを作り、しかしプログラマはその機能の内殆どを使っていないのです。だったら最初から既存のRDBとは全然違う、機能も互換性も絞ったものを使ってもいいじゃんという気持ちになりました。

既存のRDBプラグインじゃダメなのか

追加したい機能があるならプラグインを作ればいいというのはまともな発想です。
僕も最初はこれを作ろうと思いました。
PG-StromというGPUを使ってクエリをアクセラレートするPostgreSQLプラグインがあります。僕はこれを知っていたので、僕も真似しようと思いました。Firebase連携プラグインを作ろうと思ったのです。しかし、クラウドRDBマネージメントサービスが全盛の時代に、これを使ってくれる人がどれくらいいるか考えた結果断念しました。

SpannerやBigQueryではダメなのか

自分はFirebaseと連携するRDBがほしいので、公式が対応してくれない限り無理だと思います。

また、SpannerやBigQueryはいろんな分析をするためには優れていると思いますが、Webアプリケーションのバックグラウンドには向いていないのではないかと思います。
柔軟に対話的に分析をしたり、今後何に使うかわからないデータを大量に蓄積しておくにはいいサービスだと思います。しかし、多くのWebアプリはどのデータをどのように集計して使うかわかりきっている場合が多いです。事前にデータの並べ方を工夫しておけます。BigQueryのような「どんなクエリが来ても一瞬で集計してくれる」機能はオーバーキルであり、逆に課金の高さが際立ってしまいます。

作りたい機能

Firebase連携機能

先述の通り、Firebase連携機能がほしいです。
RDBのビューをFirebaseに反映できたらいいと思います。マテリアライズドビュー機能を持つDBがありますが、そのマテビューをFirebaseに書き込むようなものです。

よく、Firebaseを使ったリアルタイムなチャットのデモがあります。
でも、アレを作るのって結構大変です。
まず、セキュリティルールを正確に記述しなければいけません。
また、Firebaseは非正規化して使うのが普通なので更新系が苦手です。
新規書き込みをリアルタイムで反映させるのは簡単でも、書き込みユーザーがユーザー名を変更した時に画面上のユーザー名をリアルタイムで更新するのは諦めたりします。

RDBはこういう操作が得意です。
だから、RDBに対してCREATE VIEW文を宣言したら自動的にそのVIEWがFirebaseに反映され、DBが更新されたらリアクティブにFirebaseも差分更新されたらいいなと思いました。
この機能を作りたいです。

新しいSQL

僕はRDBSQLの世界観が好きです。
ジョー・セルコの「プログラマのためのSQL」を愛読しています。
しかし、SQLの文法はあまり好きではありません。
今のSQLの文法ではSQLが本当にやりたかったことを満たしづらいと思います。だから新しい文法を作りたいです。
詳しくはこっちです。
https://blog.isyumi.net/entry/2018/08/01/140842

定理証明

最近既存のOSSの実装を定理証明することでバグを発見するような試みを聞くことが多いです。
先日話題になったHeartbleedもその一種です。
発見される側の気持ちはわからないですが、切り口として面白いと思います。
僕は最初からこのRDBの実装に定理証明を導入したいです。
RDBは平行処理とステートマシンであり、定理証明の得意分野の一つだと思います。
最初から定理証明があるということが、コントリビュータを増やすことに効いてくると思います。
もちろん、定理証明があるかないかはユーザーにとって関係ありません。自己満足のそしりを免れられないと思います。
しかし、そもそも僕が作りたいから作るのであって僕が定理証明を入れたいなら入れるのが正義なのです。異論は認めません。

管理ツール

MySQL Workbenchのような使いやすい管理ツールを作りたいです。これがキラーアプリになると思います。
僕は普段Webフロントエンド系の開発をすることが多いので、こういうのは得意です。

課題

KVS上に参照整合制約やユニーク制約やON DELETE CASCADEは簡単に作れると思もいます。

一番大変そうなのはVIEWです。特定のテーブルの更新に追従して別のテーブルに実体化させておいたVIEWやFirebaseを最安経路で更新するアルゴリズムを考えるのが大変だと思います。
例えば月ごとの売上VIEWであれば、

  • 初回起動時に全ての売上
  • 売上が立ったらその月の合計を増やす
  • 売上がキャンセルされたらその月の売上を減らす
  • 売上の日付が月をまたいで変更されたら
  • 元の月の売上を減らして変更後の月の売上を増やす
  • 個数が変更されたら単価*(変更後の個数-元の個数 )を加算

を導出する。
どうやったらいいのかさっぱりわからない。

じゃあいつやるの?

現行のスケジュールでは来年の5月頃に取り組めるかと思います。

インメモリアーキテクチャ

 

 

僕はサーバーサイドのビジネスロジックの書き方について、メモリに全データを載せておいたほうがいいのでは、と考えています。

理由

クエリを書いたりキャッシュを設計したりするのがめんどくさいからです。もちろん僕は、SQLを書いたりキャッシュを設計したりすることができます。おそらくどちらかといえば上手な方だと思います。しかし、自分がやらなければいけない仕事がたくさんある中で、あまりそれに時間を使いたくないということがあります。

とりわけ、最近はパフォーマンスのためにKVSを組み合わせることが多くなりました。キャッシュ目的のKVSを設計するのは大変です。

  • そのデータがどう使われるのか
  • どのタイミングで更新されうるのか
  • マイグレーションはどうするのか

を考えぬいて正確に設計しなければいけません。

パフォーマンスのために開発時間が余計にかかることはよくあります。素朴に実装していいなら30分で終わるけどパフォーマンスのために色々キャッシュを設計して書いたので2時間かかったりします。

それであれば、お金はかかるもののメモリをいっぱい積んでいるインスタンスを借りて、ビジネスロジックに必要なデータは全部インメモリに載せておくのが一番ラクなのではと思いました。

文脈

オンラインゲームではそれをやってるらしいです。
DBにはライトスルー方式で書き込んでいるそうです。
https://www.amazon.co.jp/dp/4774145807
これを見てWebアプリケーションもそうすればいいのではないかと思いました。

メリット

  • DBからデータを取ってくる部分のコードを減らせる
  • パフォーマンスのために複雑なコードを書かなくて良くなる
  • テストコードを簡単に書ける

デメリット

  • メモリがたくさん必要でお金がかかる
  • そもそも一定以上の大規模サービスでは不可能
  • 本番環境のインスタンスの立ち上げに時間がかかる

実現方法

複数インスタンスをどうやって同期するか

RDBレプリケーションプロトコルを喋れればいいかなと思います。
バイナリログをListenするのがもっと簡単かもしれません。
Firebaseという手もあると思います。

どんなコードになるのか

サーバーのAPIは純粋関数になります。
引数はHTTPリクエストとRDB上の全データです。
このデータはImmutableです。
戻り値はHTTPレスポンスとRDBへの更新命令です。
フレームワークはDBへの変更を監視し、常に手元の変数を更新します。
HTTPリクエストが来たら関数を適用し、戻り値を得ます。
まずはDBに対して更新を試みます。
成功したらHTTPレスポンスをブラウザに返します。

パフォーマンスは大丈夫なのか

メモリ量については、大半のWebアプリにとっては大丈夫だと思います。
大半のWebアプリは数十GB以内で済むのではないかと思います。

CPUもそこそこ大丈夫ではないかと思います。
Go言語で100万件の中からFor文で条件に合う数件を抽出するコードは、だいたい数十msくらいだと思います。

将来はいろんな自動最適化が出来るようになると思っています。

更に

フロントエンドプログラミングとされているものも、サーバーに置けるかもしれません。
RDBのデータを全部インメモリで持っておくなら、フロントエンドのステートもサーバーのメモリに持っていてもいい気がします。
うまいこと楽観的UIを透過的に扱う方法があれば、これが一番ラクだと思います。
DOMベースでUIをサーバーから更新するのか、あるいはブラウザのCanvasに映像を流すのか、どちらも可能だと思います。後者は楽観的UIと相性が悪そうな気がなんとなくするのと、ドキュメントののセマンティクスが失われるので、あまり気分は良くないです。

いつからやるの?

僕は、趣味の開発を除けば、そういうのがベストプラクティスとして確立するまではやらないです。
でも、そのうちそういうフレームワークが出てきてこなれてくると思います。そうしたら始めます。

プライベートプロキシサーバーが流行るかも

どうもSquid派のいしゅみです。
僕は「きっと10年後にみんなこんなことしてるんだろうな」みたいなことを考えるのが好きです。

最近、各自レンタルサーバーにHTTPプロキシサーバーを立てるのが流行る未来が来るかも、ということを妄想しています。
そういう層があることがいろんな便利さにつながる気がします。

プロキシサーバーといえば大企業に行くと凄く遅い社内プロキシの使用を強要されて腹が立つアレです。
アレがもっと高機能化したら便利だなという話です。

きっかけ

apt installとかnpm installとかpub getとかdeq ensureとかcomposer installとかをするたびに、ローカルPCとEC2上のインスタンスでは全然待ち時間が違うんだなと思いました。
クラウド事業者内のネットワーク〜各ISP〜自分のISP〜自分のルーター〜自分の端末のどこに崖があるのか知りませんが、ゲートウェイのあっち側はきっとめっちゃ速い世界で繋がってるのだろうと思いました。
そこで、ネット上のどこかに自分のWebブラウザの代理をしてくれるものを立てたら、いろんな高速化が可能だぞと思いました。

どこに立てるのか

おそらく自分のISPホスティングサービス内に置くのが一番速いと思われます。
僕は名古屋に住んでいて、名古屋から出ることは極めて稀で、スマホの4Gも自宅の光回線So-netなので、(MVNOは実際にどういう実装なのか知らないですが)名古屋市内にあるSo-netホスティングサービスにプロキシサーバーを置いておくのが一番いいのではないかと思います。

どんな最適化が可能か?

このプロキシでできるようなことは、ほとんどコンテンツ提供者側の努力で実現可能だと思います。
そこで、本来はコンテンツ提供者側がやるべきだが自衛のためにすることと、パーミッションの問題でコンテンツ提供者側がどんなに努力してもできないことを別々に書くことにします。

大雑把に

  • 「端末↔自分のプロキシ」はそこそこ遅い
  • 自分のプロキシと各コンテンツのサーバーの通信は高速
  • ただし、コンテンツサーバーの処理の速さは千差万別
  • プロキシを挟むことによるオーバーヘッドは無視できるものと考える
  • 「端末↔自分のプロキシ」と「端末↔そのへんのCDN」はほぼ同じレスポンスタイム
  • 端末は電池・CPU・メモリ・ネットワークが常に逼迫しており、HDDも豊富ではない
  • プロキシは電池・CPU・メモリ・ネットワークが潤沢で、ほぼ無尽蔵のHDD領域を持つ
  • 良いコンテンツ提供者ほど、お客さんのプライバシーは持ちたがらない

という仮条件を設定して書きます。

1.出来の悪いWebサイトに対してできること

まず、画像の圧縮が可能です。同じ話でCSSやJSの圧縮が可能です。

HTMLの中を見たりJSをドライランすることにより、今後必要なコンテンツを先行読込できます。

次に、拡張機能を注入するレイヤーに使えます。現在AndroidiPhoneのブラウザは拡張機能に対応していないですが、このレイヤーで何かコンテンツをいじったりできれば、広告ブロックやオリジナルショートカットキー設置などができます。

Httpヘッダーでキャッシュ期間を指定しても、実は端末には数日しか残っていないという話がありますが、ここに大量のキャッシュを置いておくことができると思います。

2.出来の良いWebサイトに対しても有効なこと

プロキシサーバーに端末のサイズや表示可能な画像形式を登録しておけばメディアクエリやpictureタグの先行解決ができるかもしれません。
他にもトラッキングにつながるという理由で禁止されているいろんな機能の中に、このレイヤーなら出来ることがあるかもしれません。
特に最近リファラ固定化の流れがあるので、その影響で出来なくなったユーザーにとっては便利な機能をここに入れられる気がします。

どれだけの効果があるかわからないですがHttpOnlyクッキーは端末とやり取りしなくて良くなるのでちょっとレスポンスを削れます。
他にも探せば何か「プロキシ↔端末」間で省けるデータがあるかもしれません。リファラとか。

端末がプロキシと今どんなキャッシュをローカルに持っているのかについて、密に連携しておくことによってCache-DigestやHTTP2のPushに相当する機能をオーバーライドできるかもしれません。

圧縮アルゴリズムに詳しくないですが、ある程度いろんなファイルをまとめて圧縮したほうが圧縮効果が高いのではないかと思います。
そこで、オリジンをまたいでいくつかのサイトをまとめて圧縮できるかもしれません。

暗号論にも詳しくないのですが、何らかの方法でプロキシと端末間で鍵を束ねることができれば、端末からの鍵交換回数を減らせるかもしれません。

その他のありそうな便利機能

機械学習的なことに使えると思います。
というのも、自分のログを分析業者にどんどん送れば便利になるかもしれませんが、それはプライバシーとの戦いです。
そこで、自分のログを自分のサーバーにどんどん蓄積してそこで自分で分析すれば自分の傾向から良いサジェッションが出来ると思います。

パスワード管理や個人情報オートコンプリートの保存場所に使えます。
Chromeが各サイトで自動で住所やクレカ情報を入力してくれるのでありがたがっているのですが、やっぱりこれは自分のコントロール下に置けたらなと思います。
そこで、プロキシにそういう機能をもたせることもあると思います。
同じ話で、ブックマークも共有できると思います。

Cookieを共有することで、各サイトへのログインを共有化できそうです。
PCでログインしたら出先でスマホを開いてもログインできているようにできそうです。

画像の保存機能を進歩させられそうです。
今、良い画像を見つけた時にローカルに保存するか、(僕みたいな人は)Twitterにリンクを載せてメモ代わりにすると思います。
前者はその後他の端末で見る時に困りますし、後者はリンク切れの心配があります。
そこで、ワンクリックでその画像をプロキシ内に保存することができたら便利です。

まとめ

便利そうです。
他にも便利そうな機能を思いついたらコメントとかTwitterにリプとかください><。

Notification as a Serviceが求められる

文脈

現在のWeb開発現場では

が使われるようになった。

これを、Webアプリケーション開発者はビジネスロジックの開発のみに集中しそれ以外のめんどくさいことは各クラウドサービスに外出しする流れと総括できるだろう。
(WebアプリにはAndroidアプリやiPhoneアプリも含む)

僕はいま、Webアプリにおける通知もこの仲間に入っていくであろうと予測している。
そのことについて書く

通知とは

ここでは以下のものを通知と呼ぶ。

まず、ServiceWorker、iOSのAPNS、AndroidFCSを使ったプラットフォームのネイティブのサーバー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してくれるようにできる。