ウェブアプリケーション
ウェブアプリケーション(Web application)は、ウェブ(World Wide Web)技術を基盤としたアプリケーションソフトウェアである。
代表的なウェブアプリケーションでは、WebブラウザがHTTPを利用してHTMLを取得・表示、それをDOMを介してJavaScriptが操作し、必要に応じてWebサーバと通信をおこなってデータを更新する。このようにウェブ(World Wide Web)を基盤として作られる応用ソフトウェアをウェブアプリケーション(Webアプリ)と総称する。上記の例はあくまでウェブアプリケーションを実現する技術スタックの一例であり、他の様々な技術を用いてWebアプリを作成できる。またウェブアプリケーションの明確な定義は存在しない(動的なウェブページとの差異は不明瞭である)。
ウェブアプリケーションの一例としては、ウィキペディアなどで使われているウィキやブログ、電子掲示板、銀行のインターネットバンキング、証券会社のオンライントレード、電子商店街などネット販売のショッピングカートなどを挙げることができる。
ウェブアプリケーションに対して、ローカルのデスクトップ環境上で動作するアプリケーションは、デスクトップアプリケーションやスタンドアロンアプリケーション、スマートフォンで動作するアプリケーションはネイティブアプリと呼ばれる。
Webアプリはクライアント-サーバーモデルを基本としており、WWWを基盤とする分散コンピューティングの一形態ともみれる。2010年代後半には多数のマイクロサービスをAPIを介して連携させ構成されるWebアプリも増えており、分散コンピューティングとしての側面がより強くなっている。
Webサーバ上のファイルを更新するだけで、クライアントはHTTPアクセスするだけで最新のウェブアプリケーションを利用できる。
Webサーバで処理を行って出力結果のファイルをクライアント側(ウェブブラウザ)で表示するだけなのでクライアントはウェブアプリケーションをインストールする必要はない。
各クライアント側の環境が違っていてもウェブブラウザがあればクロスプラットフォームに対応できる。
Webアプリの発展に伴って、問題点が解決し、また新たな問題が提示されるという流れが続いている。従来指摘されていたデメリットと提案されている解決技術の例は以下である。
標準機能でメディア再生が困難: HTML5 HTMLMediaElementによるメディア再生・制御
Webサーバ障害時・通信途絶時に利用不可: PWA(install + Service Worker API)によるオフライン動作
ネイティブアプリに比較して遅い実行速度: WebAssemblyによるネイティブ水準コード実行
1990年にWorld Wide Webが登場しその後ウェブアプリケーションが発明されてから、アプリケーションとしての性能・利便性・UXを高めるために長年にわたり技術開発がおこなわれてきた。
ウェブが誕生した時点ではウェブは静的Webサイトがハイパーリンクでつながれたもの、すなわちWebサーバ上に配置したHTMLファイルをウェブブラウザで閲覧しリンクを飛んでネットサーフィンするものであった。その後CGIの登場により、ユーザからの入力に応じたHTML文書などのリソースの動的生成・返却が可能になった。これにより様々なウェブアプリケーション(あるいは動的Webページ)を構築できるようになった。
CGI以後、Java ServletなどのJava EEやApache HTTP Server用のモジュールとしてPHPで記述されたプログラムを実行するmod_php、マイクロソフトが開発したActive Server Pagesなどが存在した。その後Ajax、Adobe Flash、HTML5などが登場した。
2019年現在では特にスマートフォンアプリの分野において「ネイティブアプリと同等な体験の提供」を目的としたプログレッシブウェブアプリと呼ばれる標語に基づいた技術群が精力的に開発されている。またクラウドコンピューティングの発展に伴って、自前のWebサーバではなくフルマネージドクラウドサービスをバックエンドに利用したWebアプリの開発が一部の分野では可能になっている。
サーバとクライアントの間の通信手段は伝統的にHTTPが利用される。HTTPはステートレスなプロトコルであるため、HTTPだけでは状態の管理は行えない。しかし、大半のウェブアプリケーションではセッションの管理が必要であるため、Cookieなどを用いてサーバとクライアント間でセッションIDの受け渡しをし、セッションの管理を行っている。
原則として、Webアプリは特定の言語に束縛されない。バックエンド(サーバーサイド)は開発者が任意にプログラミング言語を選定できる。フロントエンド(クライアントサイド)でもDOMは言語中立な仕様であり、またWebAssemblyを介したC言語やRustを用いた開発も原理上は可能である。ただし実態として、フロントエンドはJavaScriptあるいはTypeScriptをはじめとしたAltJSが主流となっている。
従来のWebアプリではHTMLは巨大な1つのHTMLファイルであった。Webアプリの規模拡大に伴ってモジュール化の必要性が高まり、HTMLカスタム要素をはじめとするWeb Components技術によってHTMLの分割が可能になった。
またHTMLの更新はDOMを介した手続き型プログラミング(element.setAttribute()など)によっておこなわれてきたが、宣言型プログラミングとtemplatingによるHTMLの記述(例: lit-html、JSX)がおこなわれるようになってきている。
要素のコンポジション(合成)は子要素によって実現される。親要素での子要素へのアクセスは、Web標準ではWeb Componentsのによる自動挿入と.assignedElements()による操作が提供されている。Reactではコンポーネント引数のprops.childrenが用いられ、子要素以外にも任意の属性props.xを用いることもでき、子要素の操作はReact.Childrenのメソッドで提供される。
ほとんどのWebアプリはHTMLを基盤技術としており、WebブラウザはDOMを介してドキュメントへのアクセスを可能にしている。Webアプリに求められる性能が高まるにつれて従来のDOM更新速度が不十分になり、DOM更新に関わるいくつかの技術が登場した。仮想DOM(Virtual DOM)、DOM templating(lit-html)はその例である。
サーバとクライアントの間の通信手段は伝統的にHTTPが利用される。ただしサイバーセキュリティの重要性が高まりHTTPSがデファクトになりつつある。HTTPを基盤としてより上位の通信プロトコルとしてはgRPC、リアルタイム通信用途ではWebSocketが用いられる。またHTTPとは別系統のリアルタイム通信プロトコルとしてWebRTCも用いられる。より良い通信効率・リアルタイム双方向通信を実現する次世代のプロトコルとしてHTTP/3(HTTP over QUIC)、QUIC等が開発されている。
Webアプリはクライアント・サーバーモデルを基本としており、サーバーへのリソースリクエストを高い頻度でおこなう。より高速な動作、ネットワーク途絶下での動作を目的に、リソースのコピーを提供するキャッシュが要件に合わせて用いられる。以下のように、キャッシュはクライアントからサーバーバックエンドまで様々な段階でおこなわれる。クライアントに近ければ近いほどネットワーク遅延は小さくオフライン動作に強く、一方で同期の難しさも発生する。
client-side proxy: Service Worker API
ブラウザキャッシュ:(HTTP ETag)
Web Proxyキャッシュ
DB in-memoryキャッシュ
キャッシュは原理上、同期の問題を常に抱える。なぜならキャッシュとは基本的にコピーされた時間的に古いリソースの提供だからである。ゆえに各Webアプリの要件に合わせた採用が求められる。またPOST時にキャッシュへまず書き込みその後にキャッシュとバックエンドを同期する方式もある。この場合でも同期・競合の問題は原理的に存在する。
同期の手法として差分同期(delta sync)がある。同期のもっともシンプルな方法はキャッシュのリフレッシュ、すなわちすべてのデータを再取得する方法である。充分な計算・通信リソースがあるならば全てのデータが最新であることを容易に保証できる。しかしリソースに制限がある場合、キャッシュと最新データの差分(delta)のみを更新することで制限を解決できる。delta sync方式では複数のクエリ、BaseQueryとDeltaQuery(+SubscriptionQuery)が存在する。BaseQueryはキャッシュリフレッシュに相当する全件取得であり、DeltaQueryはデータソースからの差分情報取得である。データソースは更新情報をデータとは別に持っておき、DeltaQueryに応じて更新情報Queueから情報を送り出す。これにより既存データへのアクセスリソースを抑えながら同期が可能になる。DeltaQueryは定期的なfetch実行によって実現でき、リアルタイム更新を求めるならWebSocket等を用いたsubscriptionによる差分情報購読によっても実現できる。
Delta Syncを実装するうえではBaseQueryとDeltaQueryの使い分け、オフライン対応が重要な問題になる。ネットワーク途絶が発生した場合はBaseQueryしなおす設計にするのか、DeltaQueryのリクエスト頻度はどう制御するのか(c.f. exponential backoff+jitter)、Subscriptionの再接続は誰が責任を持つのかなどである。またデータソース側でどう差分情報を生成し保持するのか(生成方法、保存期間・形式)なども重要である。
伝統的にはID・パスワードによる認証AuthN/認可AuthZを用いたアクセス制御がおこなわれてきたが、セキュリティと利便性の観点からトークンベースの手法に移行しつつある。ソーシャル・ログイン・OAuth・OpenID Connect等に対応したクラウドサービスが提供する認証・認可サービス(IDaaS)がしばしば利用される。
Webアプリではしばしば、データ更新に伴うUI更新・UI操作によるデータ更新をデータバインディングによって暗示的におこなう。React・LitElementなどのフロントエンドフレームワークがデータバインディングを担っている。宣言的に構築したHTML(likeな)UI定義にデータを混ぜることでデータバインディングを実現する場合が多い。
Webアプリでは外部データベース等へのデータアクセスがしばしばおこなわれる。リモートデータの場合、クライアントはが基礎技術としてあり、データ側のスタイル・仕様としてはREST、GraphQLが存在する。データスキーマ仕様にはRESTに対応するOpenAPI Specificationがあり、GraphQLは仕様にスキーマの定義がある。
より良いアプリケーションを目指しWebアプリは日々機能が強化され、同時に複雑性も増加している。複雑性に対応するため、様々なソフトウェアアーキテクチャが利用・考案されている。WebアプリはWebすなわちネットワークを介した分散コンピューティングとしての側面を持つため、それに応じた特性をもつアーキテクチャが選ばれる場合が多い。
複雑性に対処する方法論として「独立・自立した機能 = サービス」を連携させて大きなアプリケーションをつくる方法がある。
これに着目したアーキテクチャとしては以下が挙げられる。
マイクロサービス(microservice): 単一のバックエンド機能をサービスとして切り出して独立させる
マイクロフロントエンド(Micro frontends): 単一のフロントエンド機能をコンポーネントとして切り出して独立させる
Self-contained System (SCS): マイクロサービスとマイクロフロントエンドを含めて単一の独立機能単位(システム)として提供する
通底する思想はどれも同様で、ある1つの機能をサービス(独立機能単位)として扱い、それらの間の連携によって大きな機能を達成するという思想である(c.f. 関心の分離、分割統治、KISSの原則)。サービス同士が分離することで単一責任の原則が成立し機能の変更が1つの小さなサービスに閉じ複雑性が低減する。また組み合わせるサービスの種類によって多様なアプリケーションが構成できる。一方でサービスの連携をいかに行うかが重要になる(容易に複雑性が発生する)。機能分割が意識されないアプリケーションはモノリシックと呼ばれる。
連携にWebを用いるという構想から始まり、厳密な連携プロトコルかRESTのような柔軟なプロトコルか、開発工程・デプロイメント・チームビルディングまでを含んだ方法論か、クラウドコンピューティングサービスを前提にするかなど、範囲と標語/キャッチフレーズを変えながら、Webアプリケーションが登場した当初からアーキテクチャの提唱が続いている。プログラムの分割界面という側面では「サービスに着目」という点でドメイン駆動設計に近い思想を持っている。