メリークリスマス!
この記事はFirebase Advent Calendar 2019の25日目の記事です。
これはなに?
この1年、本を書いたり勉強会で登壇したりいろいろやってみた結果を振り返ってみると、本当に多くの人がFirebaseにふれるようになったなぁと思います。圧倒的な開発者体験の良さをもってバックエンドの関心事を一手に引き受け、アプリケーション開発を劇的に高速化してくれるソリューションとして、Webアプリでもモバイルアプリでもバックエンド第一の選択肢として確固たる地位を確立しつつあるのではないでしょうか。
それ自体はとてもいいことなのですが、Firebaseの強さを活かすためのアーキテクチャに関するアイデアはあまり表に出てきていないのではないかとも感じています。たくさんの人が「Firebaseはクセがあって使いにくい」というのを耳にしてきました。僕はそれでメシを食っているので秘密にしておきたいという気持ちもあるのですが、情報が少ないことが原因でFirebaseをうまく使いこなすことが出来ず、ネガティブな印象をもってしまう人がいるのはとても悲しいことです。
この記事では、Webアプリケーションやモバイルアプリのシステムアーキテクチャの変遷を紐解き、これまで我々が取り組んできた課題とそれらを解決するためのシステムアーキテクチャについて考察というか、僕が思う2019年末時点でのベストプラクティスを紹介します。
その中で、Firebaseがどのような役割を果たすのか、他の(m)BaaSと比べてFirebaseのどんな点が優れているのかを詳らかにします。
この記事が「Firebase使ってみたい!」という人の後押しになることを願いつつ、「すでに使っているよ!」というみなさんがよりよいFirebaseの使い方について考えるきっかけとなることを願っています。
アーキテクチャの歴史と変遷
それではさっそく、アーキテクチャの歴史を紐解いていきましょう。
この節に書かれている内容は分量の都合によりところどころ端折っています。その結果として誤解を招く表現とかむやみに強い表現が用いられている可能性がありますが、その点はご了承ください。
また、以降の文中にたびたび業務という言葉が出てきますが、仕事以外でアプリ開発をされている方もいると思います。適宜、アプリによって実現したい機能あたりに読み替えてください。
古代: モノリシックアプリケーション
初期のWebアプリケーションはモノリシックなアプリケーションがサーバサイドで動いているような構成です。
当時はクライアントPCの性能が非常にしょぼく、Webサーバは完成されたHTMLを返すことが義務であり、ユーザの端末上でJavaScriptを実行することは邪悪な行為であると考えられていた厳しい世界でした。Webサーバというのは特別なハイスペックマシンであり、アプリケーションを動かすための様々な処理はサーバサイドで完結するのが常識でした。
実際のところ、当時は他に妥当なアーキテクチャの選択肢がなかったのですが、現代でもAWS上にEC2でモノリシック構成をとっているアプリケーションは枚挙に暇がありません。言ってみれば、それだけこのアーキテクチャはよくできていた部分があるということです。
一方で、みなさんもご存知の通り、このアーキテクチャはさまざまな問題点を含んでいます。
保守性の低さとか耐障害性の低さとかあげればキリがありませんが、最も顕著なものはスケーラビリティの低さではないでしょうか。
かつて一般的であったオンプレミス環境では、バックエンドのサーバは想定しているアクセスのピークに耐えられる台数を用意します。当然ながら(現代ではさほど珍しくない)想定を超える急なアクセス増加があったときにスケールアウトして対応するということはできませんし、逆に穏やかな平常時に台数を気軽に減らすことも出来ません。
後述するサーバサイドへの要求の変化も相まって、今後モノリシックな構成を敢えて選ぶとしたら極めて特殊な事情があるケースに限られます。
中世: サーバサイドAPIの誕生
中世における象徴的な出来事としては、スマートフォンの普及とモバイルアプリの隆盛があげられます。およそ2007年頃には従来と比較して高い処理性能をもつ端末が普及し、サーバサイドですべての処理を抱え込むことの合理性が失われていきます。
画面表示に関わるロジックの大部分をアプリ側(つまりユーザの端末上)で実行できるようになったことは、その後のシステムアーキテクチャに大きな影響を与えた変化といえるでしょう。
サーバサイドアプリケーションの役割は、セキュリティの都合により隠蔽しなければならない業務ロジックの実行とデータの提供に移り変わっていきます。こうした背景によって、サーバサイドアプリケーションの新たな姿としてWeb APIが誕生します。
当時は従来のWebアプリケーションも現役ですから、次の図のようにWebアプリとサーバサイドAPIが共存したような構成をとることがしばしばありました。
もちろん(現代の知識を有する我々から見て)こんないびつな構成のアプリケーションがなんの問題も起こさないわけがなく、Webのサーバサイドアプリとモバイルアプリに業務ロジックが散ったり重複したりして保守性が大変なことになったり、そもそもロジックの整合性を維持するための二重開発が辛かったり、APIをスケールしないオンプレミス環境で稼働させることの無駄が顕著になってきたりします。
また、WebアプリでもJavaScriptをガンガンつかっていくことに抵抗がなくなってくるのもこの時期です。Ajaxがブームになり、ページ遷移させないと何も出来ないWebアプリのユーザ体験の悪さが目につくようになります。
近代: マイクロサービス
近代(およそ2014年頃〜)にはいると、モバイルアプリのさらなる繁栄に伴いWebアプリの重要度が相対的に下がっていきます。
その影ではWebのフロントエンドフレームワークが大幅な進化を遂げていきます。
今度はWebアプリがモバイルアプリを模倣する流れが生まれ、シングルページアプリケーション(SPA)と呼ばれる形態を取るWebアプリが誕生します。SPAはモバイルアプリと同様にクライアント端末上でUIを描画するロジックが動作することが特徴であり、若干の差異はあるもののモバイルアプリとかなり似ているアーキテクチャを採用できるようになります。
これにより、サーバサイドアプリケーションの主な役割はAPIを介したデータプロバイダへと変化します。モノリシックな構成はもはや必要なくなり、業務ドメインごとにAPIを束ねたマイクロサービスアーキテクチャという概念が人気を集めます。パブリッククラウドのスケーラビリティの恩恵がうけられるようになり、従来の数々の課題が解決されます。
ところが、結果だけを見ればこのマイクロサービスもうまくいかなかった寄りの評価をせざるを得ません。業務ドメインに特化したために、クライアントで動作するアプリケーションからの使い勝手が悪くなってしまったことが原因の一つです。
通信の線が増えたり、API呼び出しの順序制御とエラーハンドリングが難しかったり、APIを正しく呼び出すための知識(つまりロジック)がクライアントアプリケーション内にごちゃごちゃ散らかったり、そもそも適切なマイクロサービスへの分割統治ができていなかったりと、新しい問題が発現してきます。
また、別の問題としてはマイクロサービスアーキテクチャ自体が人類には早すぎたというのもあります。
特に中・小規模の開発組織ではその恩恵を受けることが難しく、近代後期ではたくさんのなんちゃってマイクロサービスが生まれ、そして(担当エンジニアが)死んでいく様子をちらほら観測することができます。
そんなマイクロサービスの抱える問題を解決するために、Backend for Frontends(BFF)というAPIのオーケストレーションを役割とするレイヤーが生まれます。
これによりクライアントからAPIが呼び出しやすくなることが期待されましたが、実際にはBFFの肥大化が起こった(結果モノリシック時代と類似した問題を抱えた)り、BFF自体がシステム上の結合の中心≒単一故障点になったり、APIオーケストレーションと業務ロジックの境界がはっきりしないためにコードの無法地帯が生まれたりと、これもまた新しい問題と向き合う結果になります。
Firebaseと生きる令和の時代
そんなこんなで、令和の時代がやってきました。
数々の地雷を踏みぬいたエンジニアたちのおかげで、今日のアプリケーションアーキテクチャはかなり洗練されてきています。パブリッククラウドに代表される周辺技術の進歩もあり、古代に目にした数々の問題はもはや取るに足らない物となってきています。
一方で先に説明したとおり、近代のアーキテクチャが新しい課題を生んでしまったことは無視できない事実です。
これらの解決に取り組むにあたって、クラウド技術に支えられたサーバレスアーキテクチャ1やSOLID原則の重要性に異を唱えるエンジニアはほとんどいないと思います。
我々が使うべきツールはこれまでの経験を通して明らかになっており、あとは解決すべき問題の正体を捉えるだけです。
平成のアーキテクチャは何がダメだったのか
さて、その正体とはなんなのでしょうか。
これに関しては様々な議論があると思いますが、我々人類の最大の失敗はREST APIを過信したことにあると考えています。
当たり前過ぎて忘れてしまいそうですが、REST APIの欠点は呼び出すまでなにもしてくれないことにあります。
REST APIとは、多くの社会人が忌み嫌っていたはずの指示待ちを本質とするシステムなのです。
我々がREST APIを開発するときに暗黙的にやってしまいがちな過ちとして、1回の呼び出しで関連する業務をすべて完了させようとしてしまうことがあげられます。
しかし、これはシステムの保守性を考えると仕方のないことでもあります。
「関連する業務」をどのように処理するかをクライアントに知識として持たせるのは、疎結合・高凝集の観点に立つと(おそらく)間違いです。その複雑さをAPIに肩代わりさせ、1回のAPIの呼び出しの背後にすべてを隠蔽するというのは、そのAPIが複雑な結合の集合点になってしまうというという問題を除けば合理的な判断です。
余談ですが、このようなシステムを人間の世界に例えれば、1人のすべての業務を知り尽くした超優秀なリーダーがたくさんの指示待ち人間を引っ張って業務を遂行するのと似ています。
コンウェイの法則とはうまいことを言ったものです。
もしシステム化しようとする対象の業務を正確にモデル化したならば、そのなかに「指示待ち」のコンポーネントはほとんど存在しないはずです。つまり、REST APIを採用することを決めた時点で、現実世界とシステムの乖離が起こることを(知らず知らずのうちに)妥協してきたのです。
これこそが我々が平成の間に解決できなかった問題であり、今なお技術的負債の源泉としてエンジニアを苦しめるものの正体です。
Firebaseと共に目指す未来
令和の時代に我々がシステムアーキテクチャを描くときに大事にしなければいけないことは、「いかに現実の世界をそのままのカタチでシステムにするか」であるといえます。物理屋さんが現実世界を正しく記述するために難しい数式をこねくり回したり、機械学習屋さんが高性能なモデルを得るためにハイパーパラメータを頑張って調整したりするのと同様です。我々アプリケーションエンジニアも現実を正確にモデル化することをサボっている場合ではありません。
とはいえ、そのようなシステムを構築するのは非常に難易度が高かったことも事実です。ところが平成の終わり、ついに我々はこの問題を解決するためのを手に入れました。アーキテクチャを現実に寄せることを強力にサポートしてくれるそのツールは、名前をFirebaseといいます。
Firebaseの話をすると、各サービスがアプリケーションの必須機能の開発を強力にサポートしているとか、オフラインでも堅牢な動作をするアプリケーションが作れるとか、そういった一見してわかる部分に注目が集まりがちです。
しかし、真に注目すべきポイントは、開発者にとってはサーバレスなマネージド・サービス群として振る舞う一方で、内部では高度なメッセージングシステムを備えており、ユーザが行うあらゆる操作に反応して自律的に業務ロジックを実行する基盤が用意されているところにあります。
これによってシステムからは指示待ちコンポーネントも超優秀なリーダー2も排することが可能になり、我々が目指す「業務を正確にモデル化する」ことが現実に可能になるのです。
少し視野を広げてみると、フロントエンド界ではすでにこのようなシステムが普及している事に気づきます。状態が与えられることによって各コンポーネントが反応する、いわゆるリアクティブシステムというやつです。
昨今のフロントエンドフレームワークはいずれもリアクティブシステムを特徴として備えており、Firebaseの各サービスと組み合わせることでフロントエンドからサーバサイドまで一気通貫したリアクティブなシステムの構築が可能になります。
この図はちょっと抽象度が高すぎますが、これが次に我々が目指すべき、手の届くところにある未来のシステムの姿ではないかと考えています。もしかしたらFirebaseに頼らなくてもこのようなアーキテクチャを実現することは出来るかもしれませんが、現実的な難易度ではないなと思っています。
Firebaseの使い方
というわけで、Firebaseを上手に使うには従来のアーキテクチャとはまったく異なる考え方を採用することが(たぶん)求められます。Firebaseを使ってこれまでと同じものを作ろうとしてもそもそも思想が異なるのでうまくいきません。「Firebaseはクセがあって...」系の原因はだいたいこの辺にあるのではないかと思います。
ここでは、Firebaseを中心に据えたアーキテクチャで核となる役割を担う、データベースプロダクトであるFirestoreと、業務ロジックの実行基盤であるCloud Functions for Firebaseの使い方に軽く触れておきます。
Firestore
Firestoreはクライアントアプリケーションから直接アクセスを受けることが最大の特徴であり、REST APIに代わるデータプロバイダとして新しい時代のアーキテクチャにおいて重要な役割を担います。
ドキュメント指向・オートスケーリング・高い稼働率・強整合なクエリ・クライアントから発行可能なトランザクション・オフライン耐性などの特徴は、いずれもかつて我々がBFFに望んで手に入らなかったものです。
そしてもうひとつの特徴的な機能であるリアルタイムアップデートは、サーバサイドで発生したイベントをクライアントに通知する仕組みであることがその本質です。
このようなサーバからクライアントに対してなにかを通知する仕組みは、ステートレスを前提とするサーバレスアーキテクチャの思想と相性が悪く、取り入れることが難しい機能でした。令和の時代では、Firestoreを導入するだけでサーバレスアーキテクチャの特性を生かしたまま、サーバサイドからの通知を実現することが出来ます。
Cloud Functions for Firebase
Cloud Functionsは、イベントドリブンなユーザ定義関数の実行環境としてシステムに大きな柔軟性を与えます。
Firebaseでは、Firestoreへのデータ書き込みはもちろん、ユーザ認証基盤であるAuthenticationへの新規ユーザ作成、ユーザが作成したメディアの保存先であるStorageへのファイルアップロード、はたまた分析ツールであるAnalyticsへのカスタムイベントの送信など、あらゆるユーザの操作を業務ロジック実行のトリガーとして利用することが出来ます。
また、必要に応じて業務ロジックの実行結果を何らかの形でFirestoreに書き戻すことによって、クライアントに対して処理の完了を通知することが出来ます。
このようなイベントによってトリガーされるバックグラウンド関数とFirestoreを組み合わせることによって、Firebaseは我々をREST APIのような指示待ちシステムの開発から開放し、現実の業務をより正確にモデル化することを可能にします。
呼び出し可能な関数を使ってはいけない理由
さて、これまで意図的に無視してきましたが、Cloud Functionsでは呼び出し可能な関数も作ることが出来ます。しかし、呼び出し可能な関数を使うことは可能な限り避けるべきです。
この記事の前半部を読んでいただいた皆様はすでにお気づきかと思いますが、呼び出し可能な関数を使うということはREST APIへ回帰するのと同義です。特に、Firestoreの前に呼び出し可能な関数を置くのは単に従来の構成に戻るだけでなく、Firestoreがクライアントから直接アクセスを受けることによって可能にした(オフラインデータやリアルタイム・リスナーといった)数々のメリットをドブに捨てる行為なのです。
もちろん、いつでも理想的なシステムが作れるわけではありません。モノによってはやむなく呼び出し可能な関数を使うこともあるでしょう。しかし、これからの時代を生きていくためには従来のAPIありきの構成を取るべきではありませんし、どうしてもそれが必要なのであればFirebaseよりも優れたソリューションがいくらでもあります。あえてFirebaseでそれをやる必要性はありません。
まとめ
この記事では、
- これからのシステムアーキテクチャ
- Firebaseは何がすごいのか
- Firebaseをどのように活かすのか
をそれっぽく解説しました。
これまでに見てきたあらゆる技術がそうであるように、Firebaseもまた万能の願望器ではありません。Firestoreには依然としてクエリに大きな制約がありますし、GCPのサポートなしに大規模なアプリケーションを構築することは難しいでしょう。言うまでもありませんが、クライアントを持たない純粋なサーバサイドアプリケーションを作るためにFirebaseを使うことはまったくもって合理的ではありません。
大切なことは、解決したい問題を明らかにし、それを解決できる技術を選ぶということです。
この過程を怠った信念なき技術選定が誰かを幸せにすることは絶対にありません。
Firebaseがどれほど優れた機能性を備えていたとしても、使う側がそれを正しく理解していなければ動くゴミ以外のものを作ることは出来ません。
Firebaseはまだまだ進化の途上であり、ここに書いた内容がいつまで有効なのかあまり自信はないのですが、2019年の終わりにこういう事を考えていたという記録が将来誰かをを救うことを願いつつ、終わりにしたいと思います。
それでは、2020年もよきFirebaseライフを!
PR
長らく在庫を切らしていたFirestore本ですが、なんと技術の泉シリーズとして商業出版していただけることになりました!
何事もなければ次の技術書典で皆様にお届け出来るのではないかと思います。
大人の事情により書けなかったあれこれとか、修行が足りなかった当時の怪しい記述を全面的に見直し、新章を追加したりした結果ボリュームが1.5倍くらいに膨れました。
今回の商業化に伴い、電子版もご利用いただけるようになります。
こちらもお楽しみに!