ウェブアプリケーション

ウェブアプリケーション（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年現在では特にスマートフォンアプリの分野において「ネイティブアプリと同等な体験の提供」を目的としたプログレッシブウェブアプリ（英語：Progressive web app、PWA）と呼ばれる標語に基づいた技術群が精力的に開発されている。またクラウドコンピューティングの発展に伴って、自前の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の<slot>による自動挿入と.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 Cache ブラウザキャッシュ:HTTPキャッシュ（HTTP ETag） Web Proxyキャッシュ コンテンツデリバリネットワーク（CDN） BFFキャッシュ 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アプリでは外部データベース等へのデータアクセスがしばしばおこなわれる。リモートデータの場合、クライアントはfetchAPIが基礎技術としてあり、データ側のスタイル・仕様としてはREST、GraphQLが存在する。データスキーマ仕様にはRESTに対応するOpenAPI Specificationがあり、GraphQLは仕様にスキーマの定義がある。 より良いアプリケーションを目指しWebアプリは日々機能が強化され、同時に複雑性も増加している。複雑性に対応するため、様々なソフトウェアアーキテクチャが利用・考案されている。WebアプリはWebすなわちネットワークを介した分散コンピューティングとしての側面を持つため、それに応じた特性をもつアーキテクチャが選ばれる場合が多い。 複雑性に対処する方法論として「独立・自立した機能 = サービス」を連携させて大きなアプリケーションをつくる方法がある。 これに着目したアーキテクチャとしては以下が挙げられる。 サービス指向アーキテクチャ マッシュアップ マイクロサービス（microservice）: 単一のバックエンド機能をサービスとして切り出して独立させる マイクロフロントエンド（Micro frontends）: 単一のフロントエンド機能をコンポーネントとして切り出して独立させる Self-contained System (SCS): マイクロサービスとマイクロフロントエンドを含めて単一の独立機能単位（システム）として提供する 通底する思想はどれも同様で、ある1つの機能をサービス（独立機能単位）として扱い、それらの間の連携によって大きな機能を達成するという思想である（c.f. 関心の分離、分割統治、KISSの原則）。サービス同士が分離することで単一責任の原則が成立し機能の変更が1つの小さなサービスに閉じ複雑性が低減する。また組み合わせるサービスの種類によって多様なアプリケーションが構成できる。一方でサービスの連携をいかに行うかが重要になる（容易に複雑性が発生する）。機能分割が意識されないアプリケーションはモノリシックと呼ばれる。 連携にWebを用いるという構想から始まり、厳密な連携プロトコルかRESTのような柔軟なプロトコルか、開発工程・デプロイメント・チームビルディングまでを含んだ方法論か、クラウドコンピューティングサービスを前提にするかなど、範囲と標語/キャッチフレーズを変えながら、Webアプリケーションが登場した当初からアーキテクチャの提唱が続いている。プログラムの分割界面という側面では「サービスに着目」という点でドメイン駆動設計に近い思想を持っている。 高レベルの要素から低レベルのAPIまでWeb標準で提供されている。 <audio> 要素: src属性の設定のみで再生ウィジェットを表示可能 HTMLMediaElement DOMインターフェイス: JavaScriptによる詳細なメディア再生の制御 Web Audio API: AudioNodeからなるグラフをAPIにより構築する低レベルAPI Webサーバーとの通信（ネットワーク）が途絶している状態をオフラインという。ソフトウェアがオフライン時にも動作するには ローカルに存在するソフトウェアプログラム 失敗するネットワークリクエストの処理 オンライン復帰時の同期 などが必要とされる。Webアプリは一般にインストール（プログラムのローカルへの保存と組み立て）を必要としないため、オフライン状態ではそもそもアプリのプログラムにアクセスできない。そのためWebアプリではオフライン対応に特別な仕組みが必要になる。オフライン対応を前提としたWebアプリへの標語として「オフラインファースト/offline first」がある。 Webアプリでは、ブラウザがWebアプリのURLへrequestを送りそのresponseを実行することでアプリが起動・動作する。よってWebアプリのオフライン対応ではまず、ネットワークrequestをインターセプトしローカルに保存されたプログラムを代わりにresponseとして返す、すなわちプロキシとキャッシュの仕組みが必要になる。現代のWebアプリではService Worker APIのFetchEventとCacheによって実現される。Service Worker内で検知されたfetchEventに応じてrequestを止め、事前のアクセス時にキャッシュされていたresponse（プログラム）をCache storageから取り出しrequestへのresponseとする。これによってブラウザへはあたかもオンラインであるかのようにプログラムが返され、オフラインのWebアプリ起動が可能になる。 多くのWebアプリは起動後もネットワークを介した情報のやり取りを行う。オフライン時にはネットワーク途絶によりこれらのネットワークリクエストが失敗する。ゆえに 失敗リクエストの処理（適切に失敗し、アプリを落とさない） 失敗リクエストの保存・破棄 オンライン復帰時の失敗リクエストリトライ オフラインの間に外部で発生した情報との競合解決・同期 などに対応する必要がある。 例えばGmailなどのメールアプリは、適宜メールサーバーへ新着メールの問い合わせをおこなっている。オフライン時にはネットワーク途絶によりメールの送信ができなくなるが、リクエストを単に破棄すると書いたメールを捨てることになってしまう。失敗リクエスト時にメールを送信待ちリストに加えるのか、あるいは下書きに戻すのかなどが重要な設計項目になる。待ちリストに加える場合はオンライン復帰時の適切な再送信（リトライ）機能が必要である。またオフラインの間にPCブラウザのGmailから下書きを編集し、同時にスマホのGmailからも下書きに別の変更を加えた場合、オンライン復帰時に2つの相反する編集が存在するため、競合を解決・同期しなければいけない。