スケーラブルなフロントエンド アーキテクチャとは

ソフトウェア開発に関して言えば、スケーラビリティという言葉の最も一般的な 2 つの意味は、コード ベースのパフォーマンスと長期的な保守性に関連しています。両方を使用することもできますが、優れた保守性に重点を置くことで、アプリケーションの他の部分に影響を与えることなくパフォーマンスを調整しやすくなります。これは、バックエンドとの重要な違いであるローカル状態があるフォアグラウンドではさらに当てはまります。

この一連の記事では、実際にテストされた方法を使用してスケーラブルなフロントエンド アプリケーションを開発および維持する方法について説明します。私たちの例のほとんどは React と Redux を使用しますが、同じ効果を達成する方法を示すために他の技術スタックと比較することがよくあります。ソフトウェアの最も重要な部分である、このシリーズのアーキテクチャについて話し始めましょう。

ソフトウェア アーキテクチャとは
建築とは一体何なのか?アーキテクチャがソフトウェアの最も重要な部分であると言うのは大げさかもしれませんが、聞いてください。

アーキテクチャとは、ソフトウェアのさまざまなユニットを相互作用させて、下すべき最も重要な決定を強調し、重要でない決定や実装の詳細を延期する方法です。ソフトウェアのアーキテクチャを設計するということは、実際のアプリケーションをそれをサポートするテクノロジから分離することを意味します。実際のアプリケーションは、データベース、AJAX 要求、または GUI の知識はありません。代わりに、ユース ケースが実行されるロールやデータがどこにあるかに関係なく、ユース ケースと、ソフトウェアでカバーされる概念を表すドメイン ユニットで構成されます。永続化されます。

アーキテクチャについては、もう 1 つ重要な点があります。それは、ファイルの編成や、ファイルやフォルダーの命名方法を意味するものではありません。


フロントエンド開発における階層化
重要なものと重要でないものを分離する 1 つの方法は、それぞれが異なる特定の役割を持つレイヤーを使用することです。レイヤーベースのアーキテクチャでは、アプリケーション、ドメイン、インフラストラクチャ、および入力の 4 つのレイヤーに分割するのが一般的なアプローチです。これらの 4 つの層については、別の記事「NodeJS と優れた実践」で詳しく説明しています。続行する前に、それらに関する投稿の最初の部分を読むことをお勧めします。2 番目の部分は NodeJS に固有のものであるため、読む必要はありません。

ドメイン層とアプリケーション層は、テクノロジーにとらわれないため、フロントエンドとバックエンドの間でそれほど違いはありませんが、入力層とインフラストラクチャ層については同じとは言えません。Web ブラウザーでは、通常、入力レイヤーにはビューという 1 つの役割しかないため、それをビュー レイヤーと呼ぶこともあります。また、フロントエンドはデータベースやキューイング エンジンにアクセスできないため、フロントエンド インフラストラクチャ レイヤーではそれらを見つけることができません。AJAX リクエスト、ブラウザー Cookie、LocalStorage、さらには WebSocket サーバーと対話するためのユニットをカプセル化する抽象化が見つかります。主な違いは単に抽象化されたものであるため、まったく同じインターフェイスを備えたフロントエンドとバックエンドのリポジトリを使用できますが、その下にあるテクノロジは異なります。優れた抽象化がいかに素晴らしいものであるかがわかりますか?

ビューを作成するために React、Vue、Angular などを使用するかどうかは問題ではありません。ロジックなしで入力レイヤーのルールに従い、入力パラメーターを次のレイヤーに委譲することが重要です。フロントエンドのレイヤーベースのアーキテクチャに関するもう 1 つの重要なルールがあります。入力/ビュー レイヤーが常にローカル状態と同期するためには、一方向のデータ フローに従う必要があります。この用語は聞き覚えがありますか? これを行うには、特殊な第 5 層である、ストレージとも呼ばれる状態を追加します。


ステート レイヤー
一方向のデータ フローに従いますが、ビューが受け取ったデータをビュー内で直接変更したり変更したりすることはありません。代わりに、ビューから「アクション」と呼ばれるものをディスパッチします。これは次のように機能します。アクションがデータ ソースにメッセージを送信し、データ ソースがそれ自体を更新し、新しいデータでビューに再送信します。ビューからストアへの直接的なパスは存在しないことに注意してください。そのため、2 つのサブビューが同じデータを使用する場合、いずれかのサブビューからアクションをディスパッチできます。これにより、両方のサブビューが新しいデータで再レンダリングされます。特に React と Redux について話しているように見えますが、そうではなく、React + コンテキスト API、Vue + Vuex、Angular + NGXS など、ほとんどすべての最新のフロントエンド フレームワークまたはライブラリで同じ結果を得ることができます。 、または Ember でさえ、データ ダウン アクション メソッド (別名 DDAU) を使用します。イベント システムを使用してアクションを送信する jQuery を使用することもできます。

このレイヤーは、バックエンドからフェッチされたデータ、フロントエンドで作成されたがまだ永続化されていない一時的なデータ、リクエストの状態などの一時的な情報など、フロントエンドのローカルで絶え間なく変化する状態を管理します。ご参考までに、これは、状態の更新を担当するアクションとハンドラーが存在するレイヤーです。

アクションに直接配置されたコード ベースのビジネス ルールとユース ケース定義をよく見かけますが、他のレイヤーの説明を注意深く読むと、ユース ケースとビジネス ルールを配置する場所が既にあることがわかります。状態層。これは、私たちの行動がユースケースになったということですか? いいえ!はい。では、それらをどのように扱うべきでしょうか。

考えてみましょう...アクションはユース ケースではないと言いましたが、ユース ケースを配置するレイヤーは既にあります。ビューはアクションをディスパッチする必要があり、アクションはビューから情報を受け取り、それをユース ケースに渡し、応答に従って新しいアクションをディスパッチし、最後に状態を更新してビューを更新し、一方向のデータ フローを閉じます。これらのアクションはコントローラーのように聞こえますか? ビューからパラメーターを取得し、ユース ケースに委譲し、ユース ケースの結果に基づいて応答する場所ではありませんか? それがまさにあなたがそれらを扱うべき方法です。複雑なロジックや直接の AJAX 呼び出しは、別のレイヤーが担当するため、ここでは行うべきではありません。状態レイヤーは、ローカル ストレージの管理方法のみを知っている必要があります。

重要な要素がもう 1 つあります。状態レイヤーは、ビュー レイヤーによって消費されるローカル ストレージを管理するため、この 2 つがある程度結合していることに気付くでしょう。ビューがスピナーを表示できるように、リクエストがまだ保留中かどうかを示すブール値フラグのように、状態レイヤーにいくつかのビュー専用データがありますが、これはまったく問題ありません。

コード:

import api from './infra/api'; // has no dependencies
import { validateUser } from './domain/user'; // has no dependencies
import makeUserRepository from './infra/user/userRepository';
import makeArticleRepository from './infra/article/articleRepository';
import makeCreateUser from './app/user/createUser';
import makeGetArticle from './app/article/getArticle';

const userRepository = makeUserRepository({
  api
});

const articleRepository = makeArticleRepository({
  api
});

const createUser = makeCreateUser({
  userRepository,
  validateUser
});

const getArticle = makeGetArticle({
  userRepository,
  articleRepository
});

export {
  createUser,
  getArticle
};
export default ({ validateUser, userRepository }) => async (userData) => {
  if(!validateUser(userData)) {
    throw new Error('Invalid user');
  }

  try {
    const user = await userRepository.add(userData);
    return user;
  } catch(error) {
    throw error;
  }
};
export default ({ api }) => ({
  async add(userData) {
    const user = await api.post('/users', userData);

    return user;
  }
});

重要な部分、ユース ケースの createUser などは、ファイルの最後でインスタンス化され、アクションに挿入されるため、エクスポートされる唯一のオブジェクトであることがわかります。コードの残りの部分では、リポジトリがどのように作成され、どのように機能するかを知る必要はありません。それは本当に重要ではありません。それは単なる技術的な詳細です。
リポジトリが AJAX リクエストを送信するか、LocalStorage に何かを永続化するかはユース ケースにとって重要ではありません; それを知ることはユース ケースの責任ではありません。API がまだ開発中である間に LocalStorage を使用したい場合、API と対話するコードが LocalStorage と対話するコードと同じインターフェイスに従う限り、API へのオンライン呼び出しを使用することに切り替えます。ユースケースを変更する必要はありません。

数十のユース ケース、リポジトリ、サービスなどがある場合でも、上記のように手動でインジェクションを行うことができます。すべての依存関係を構築するのが面倒な場合は、結合を増やさない限り、依存関係注入パッケージを使用できます。

DI パッケージが十分に優れているかどうかをテストするための経験則は、手動のアプローチからライブラリの使用に移行する際に、コンテナー コード以外に手を加える必要がないことを確認することです。もしそうなら、パッケージはあまりにも面倒なので、別のパッケージを選択する必要があります. どうしてもパッケージを使いたい場合は、Awilix をお勧めします。コンテナー ファイルに触れるだけで、すぐに使用できます。パッケージの作成者によって書かれた、その使用方法とその理由に関する非常に優れたシリーズがあります。 

おすすめ

転載: blog.csdn.net/weixin_44786530/article/details/130194702