isyumi_netブログ

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

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

 

 

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

理由

クエリを書いたりキャッシュを設計したりするのがめんどくさいからです。もちろん僕は、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してくれるようにできる。

モックサーバーのすすめ

主張

  • SPAを作るときは、Node.js/ExpressでAPIモックサーバーを作るといい
  • SPAの動作確認・表示確認に有効である
  • 特にリアルタイム性があるアプリを数人で動作確認するのに有益

前提

本番用のサーバーはGoやScala、フロントエンドはReactなどで作る想定をしています。
本番用サーバーのコードベースに一つの機能を実装するのは時間多少時間がかかります。なので、新規開発時や機能追加時は先にフロントエンドとモックサーバーを実装することにより企画を固めてから、本番用サーバー開発に手を回すほうが合理的だと考えています。
Swaggerのモックサーバジェネレータで満足できる方は別にそれでいいと思います。
ReactのStorybookで満足できる方も別にそれでいいと思います。

SPAの動作確認・表示確認をするためにステートフルなモックサーバーがほしいことがあります。
例えばTwitterのようなSNSを作っているとして、投稿したらちゃんとタイムラインに表示されるか確認したいことがあります。
そのため、ちゃんとそれっぽい挙動をするモックサーバーを作るといいです。

やること

  1. ExpressでAPIのモックサーバーを作る
  2. webpack-dev-serverのproxy機能で1のサーバーに転送するように設定
  3. フロントエンドは普通に作る
    • 基本的にフロントエンドに特殊なコードを書く必要はない
    • ただし、本番でFirebaseを使うがモックサーバはWebSocketで済ませたい場合は、そこをすげ替える仕組みがあるといい
  4. webpackで動かして試してみる

参考までにwebpack.config.jsonを載せておきます。

let webpackHost = "0.0.0.0";  
if ("WEBPACK_HOST" in process.env) {  
    webpackHost = process.env.WEBPACK_HOST;  
}
...
devServer: {  
    contentBase: "./dist",  
    hot: true,  
    host: webpackHost,  
    port: port,  
    proxy: {  
        "/api": {  
            target: `http://${webpackHost}:${port + 1}`,  
            pathRewrite: {"^/api": ""}  
        },  
        "/ws": {  
            target: `ws://${webpackHost}:${port + 1}`,  
            ws: true  
  }  
    },  
},
...

作るモックサーバはどんなものか

データの永続化は不要です。グローバル変数に持っておけばいいです。
立ち上げたらすぐに表示を確認したいので、適当なデータを自動で生成するものを作るといいです。例えばTwitterなら100人のユーザーと100の投稿を自動で作ります。
APIエンドポイントはちゃんとそのAPIを模倣するように作るべきです。

としておくと、フロントエンドを実装したら本当に投稿できるようになっていいです。

サンプルデータをJSONでファイルに保存しておくのも良い手段です。JSONをそのまま返すのではなくて、スピンアップ時にJSONを読んでグローバル変数に入れるようにするとフロントエンドの操作に合わせて動的な反応ができていいです。

サンプルコード

// 必要に応じてサンプルデータも入れておくといいです
interface Data {  
    tweets: Tweet[];  
    users: User[];  
}

let data: Data = {  
    users: [],  
    tweets: [],  
};

router.get("/api/tweets", (req, res) => {  
    res.json(data.tweets);  
});

router.post("/api/tweets", (req, res) => {  
    let tweet = req.body as Tweet;  
    let tweetID = randomBytes(16).toString('hex');  
    tweet.tweetID = tweetID;  
  
    res.status(201);  
    res.send(tweetID);  
});

開発コストをどう考えるか

永続化をせず全てグローバル変数を使うので大変ではないと思います。
セキュリティのことも考えなくていいので簡単だと思います。
大体 RESTFul APIといえば

  • get
  • post
  • put
  • delete

です。なので一つのエンドポイントをJSの

  • find
  • concat
  • filter

を使って書けば、後は他のエンドポイントにコピペするだけです。
Swaggerを使っている人には関係ない話ですが、僕は使わないことにしたので、フロントエンドとJSONの型定義用TypeScriptファイルを使いまわせるので楽です。
なので、大してコストはかからないと思います

ファイル構成についてのアドバイス

今の所これが最善だと思っています。
リポジトリを分けることにした理由は、求められるtsconfigが違って辛いからです。
そんなことはtsconfigを2つ用意してコンパイル時に引数で分ければいいではないかと思うかもしれないですが、僕が使っているWebStormがうまくいかなくなるのでやむを得ないです。
他にも、WebStormのSuggestionが汚れて辛いとかの理由があります。

5は大変ダーティーですが、所詮モックサーバーなのでまあいいとします。
本当はフロントエンドとモックサーバーは双方向に依存していますが、package.json上で双方向に依存しているとライブラリ関係でろくなことにならないのでこうします。

  1. フロントエンドとモックサーバーはリポジトリを分ける
  2. フロントエンドがモックサーバーをインポートする
  3. フロントエンドでnpm run startするとwebpackとモックサーバーが両方立ち上がるようにしておく
  4. 開発PCではフロントエンドとモックサーバーを隣のディレクトリに並べる
  5. モックサーバがフロントエンドリポジトリ内のファイル(主にTypeScriptによるJSONの型定義ファイル)が必要な場合は ". ./隣のリポジトリ/some/file.ts"とインポートする。

本番サーバーができたら

本番サーバーが出来上がってもメンテするべきです。
なぜなら、企画→フロントエンドだけ作ってみる→実機で見て企画を練る→ というサイクルを回すために、このモックサーバーは使われ続けるからです。
GoやScala製の本番サーバは永続化層のコードを書く必要があるため、モックサーバに比べて開発のオーバーヘッドが高いはずです。常にどんな企画もモックサーバーで手早く実機確認できるようにしてあるといいです。

また、本番が始まったらassetsやマスタデータを本番から落としてくるシェルスクリプトを作っておくと、いつでもそれらしいデータを表示できるのでおすすめです。

副次的な効能

リアルタイム性があるような動き(例えば、Tweetが投稿されたら他人のタイムラインに投稿が表示されるなど)が早期に仮実装されると、企画さんが喜びます。その後が遅れちゃっても少々大目に見てくれます。フィードバックは大事です。

MySQLからリアクティブにFirebaseを更新するものを作った

コンセプトは f(RDB)=> Firebase

モチベーション

Firebase Realtime Databaseを使う上でこのような問題点がある

  • あえて非正規化した場所が、バグによって正しく更新されず不整合になるリスク
  • 正確なアクセス権限設定が極めて難しい
  • 枯れたRDBほどマイグレーションツールが揃っていないためAlter Table的な操作をしたくない

そこで、Realtime Databaseの前にRDBを置き、RDBからReactiveにRealtime Databaseを更新すれば解決できると考えた。

実装

MySQLから更新イベントを受信するためにはmysqlbinlogというコマンドを使う。
これは本来全然別のことに使うためのものだが、せっかくなので使う。

RDBの全データを引数にRealtime Database上の全データを作る純粋関数を書く。

mysqlbinlogが標準出力に何かを書いたら、それに反応してfirebaseを更新する。

サンプル

void main() async {
var command = "mysqlbinlog --read-from-remote-server --short-form --host=localhost --user=your_name --password=*** --stop-never mysql-bin-changelog.000001";

var process = await Process.start(command , []);
process.stdout.listen(onStdout);

}


class UserEntity {
int userID;
String name;
}

class MessageEntity {
int messageID;
DateTime time;
String text;
int userID;
}

void onStdout(List<int> data) async {
print("-" * 10);

var conn = await MySqlConnection.connect();

var result = await conn.executeStreamed("select * from users");

List<UserEntity> users = [];

await for (var row in result) {
var user = new UserEntity()
..userID = row[0]
..name = row[1];
users.add(user);
}

var result2 = await conn.executeStreamed("select * from messages");

List<MessageEntity> messages = [];

await for (var row in result2) {

var message = new MessageEntity()
..messageID = row[0]
..time = row[1] as DateTime
..text = new Utf8Decoder().convert((row[2] as Blob).toBytes())
..userID = row[3];
messages.add(message);
}

messages.sort((m1, m2) => m1.time.compareTo(m2.time));


var fbMessages = messages.map((m) => <String, dynamic>{
"time": m.time.toLocal().toIso8601String(),
"text": m.text,
"userName": users.firstWhere((u) => u.userID == m.userID).name,
}).toList();


await fbClient.put("/messages",fbMessages);

}

今後の課題

今はパフォーマンスが悪すぎるので改善したい。

  1. バイナリログをきちんと読み取って、毎回全データを落としてくるのではなく、関係のある行のみを落としてくるようにしたい
  2. Realtime Databaseに全データを書き込んでいるので、今回の出力と前回の出力を比較して差分更新できるようにしたい
  3. 今はRealtime Databaseに書き込む全てのデータを毎回作っているので、関数を小分けにして、無関係な部分は実行しないようにしたい

また、クライアント側からのアクションをMySQLに書き込むところも上手く抽象化したい

日本は今すぐUber Eatsのオープンプラットフォームを作るべき

Uber Eatsがよかった

今日、初めてUber Eatsを利用して見た。UXが素晴らしかった。

  • 店舗を選ぶ
  • メニューを選ぶ
  • 自分の住所を入れる
    • 料金+配送料が表示される
  • クレカを入力
  • OKボタンを押す
  • 配達員さんが今どこにいるのか地図に表示される

という完璧なUXだった。
Webアプリは簡潔でわかりやすかった。
すでにUber Eatsにハマりそうである。

Uber Eatsが解決した問題

そもそも、僕はあまり飲食店を開拓しない方だ。いつも同じ店に行っている。
新しい場所というだけで落ち着かない。
店ごとに注文方式、メニュー体系、前払いか後払いかが違う。

デリバリーも可能だが問題がある。多くの場合電話を強要される。電話が嫌なのだ。

ネットで注文できる店もある。
しかし、店ごとに

  • パスワードを作って会員登録
  • 住所入力
  • クレカ登録

をしなければいけない。
割りに合わない。
また、飲食店のホームページは店側の見せたい情報がてんこ盛りで、なかなか目当ての情報にたどり着けない。
自分に必要な情報が整然と並んでいて欲しいのだ。

しかし、Uber Eastsはすごい。

まず、今後ここに出店している店舗に注文する限り同じ会員情報を使いまわせる。
店舗数は結構多いし、ガストなどメニューが広い店舗もあるので、飽きることはないだろう。

Webアプリは、非常に整頓されていた。見苦しい宣伝文が少ないし、どんなメニューがあるのか一目瞭然だった。
料金体系も非常に的確なフィードバックがなされてわかりやすかった。

なぜ今までこれがなかったのか

僕はちょっとがっかりした。
Uber Eatsの要素を見れば、20年前の技術水準で十分に似たようなものは作れたはずである。
なぜ今まで日本の外食産業はこれを作らなかったのか。
今になって外資系に高額な手数料をふんだくられているのか。
日本のプラットフォーム構築能力の低さにがっかりする。

今からでも遅くない。すぐに取り掛かるべきだ。

するべきこと

前提として一社がプラットフォーマートして一人勝ちする構図は避けたい。
まず、各店舗がメニュー表を自分のドメインから所定のXML的なものでフィードする。
そのフィードをお客さんが自分のアプリで閲覧する。
フィードのフォーマットはオープンに定められ、誰が対応するアプリを作ってもいいことにする。
フィードは過剰な装飾ができないフォーマットになっているべきである。
お客さんは自分のアプリから注文する。
この時の注文プロトコルも事前に決めておく。
配送業者の位置情報を追跡するための鍵交換のプロトコルも必要であろう。