paint-brush
フロントエンド アプリケーションでクリーンなアーキテクチャをテストする - 意味があるか?@playerony
10,729 測定値
10,729 測定値

フロントエンド アプリケーションでクリーンなアーキテクチャをテストする - 意味があるか?

Paweł Wojtasiński21m2023/05/01
Read on Terminal Reader

長すぎる; 読むには

フロントエンド開発者は、スケーラブルで保守可能なアーキテクチャを作成するという課題に直面しています。提案されたアーキテクチャのアイデアの多くは、実際の運用環境では実装されていない可能性があります。この記事は、進化し続ける Web サイト開発の世界をナビゲートするために必要なツールをフロントエンド開発者に提供することを目的としています。
featured image - フロントエンド アプリケーションでクリーンなアーキテクチャをテストする - 意味があるか?
Paweł Wojtasiński HackerNoon profile picture

デジタル環境が進化するにつれて、最新の Web サイトの複雑さも増しています。より優れたユーザー エクスペリエンスと高度な機能に対する需要が高まる中、フロントエンド開発者は、スケーラブルで保守可能で効率的なアーキテクチャを作成するという課題に直面しています。


フロントエンド アーキテクチャに関して利用可能な大量の記事とリソースの中で、かなりの数がクリーン アーキテクチャとその適応に焦点を当てています。実際、調査対象の約 70 件の記事の 50% 以上が、フロントエンド開発のコンテキストでクリーン アーキテクチャについて論じています。


豊富な情報にもかかわらず、明らかな問題が残っています。提案されたアーキテクチャのアイデアの多くは、実際の運用環境では実装されていない可能性があります。これにより、実際のシナリオでの有効性と適用可能性について疑問が生じます。


この懸念に駆り立てられて、私はフロントエンドにクリーン アーキテクチャを実装するための 6 か月の旅に乗り出し、これらのアイデアの現実に立ち向かい、小麦と籾殻を分離できるようにしました。


この記事では、この旅の経験と洞察を共有し、フロントエンドにクリーン アーキテクチャをうまく実装する方法に関する包括的なガイドを提供します。


この記事では、課題、ベスト プラクティス、および実際のソリューションに光を当てることで、進化し続ける Web サイト開発の世界をナビゲートするために必要なツールをフロントエンド開発者に提供することを目的としています。

フレームワーク

今日の急速に進化するデジタル エコシステムでは、開発者はフロントエンド フレームワークに関して選択の余地がありません。この豊富なオプションは、多くの問題に対処し、開発プロセスを簡素化します。


しかし、それはまた、開発者の間で際限のない議論を引き起こし、それぞれが自分の好むフレームワークが他のフレームワークよりも優れていると主張しています。実際、ペースの速いこの世界では、新しい JavaScript ライブラリが毎日登場し、フレームワークがほぼ毎月導入されています。


このようなダイナミックな環境で柔軟性と適応性を維持するには、特定のフレームワークやテクノロジーを超えるアーキテクチャが必要です。


これは、トレンドの変化や技術の進歩に対応する必要がある、メンテナンスを伴う製品会社や長期契約にとって特に重要です。


フレームワークなどの詳細から独立しているため、取り組んでいる製品に集中し、そのライフサイクル中に発生する可能性のある変更に備えることができます。


恐れるな;この記事は、このジレンマに対する答えを提供することを目的としています。

フルスタックチームの協力

フロントエンドにクリーン アーキテクチャを実装するために、私は数人のフルスタックおよびバックエンドの開発者と緊密に協力して、フロントエンドの経験が最小限であっても、アーキテクチャが理解しやすく保守しやすいものになるようにしました。


そのため、私たちのアーキテクチャの主な要件の 1 つは、フロントエンドの複雑さに精通していない可能性があるバックエンド開発者や、広範なフロントエンドの専門知識を持たない可能性があるフルスタック開発者にとってのアクセシビリティです。


フロントエンドとバックエンドのチーム間のシームレスな協力を促進することにより、アーキテクチャはギャップを埋め、統一された開発エクスペリエンスを作成することを目指しています。

理論的基礎

残念ながら、素晴らしいものを構築するには、背景知識を得る必要があります。基礎となる原則を明確に理解すると、実装プロセスが容易になるだけでなく、アーキテクチャがソフトウェア開発のベスト プラクティスに確実に準拠するようになります。


このセクションでは、アーキテクチャ アプローチの基礎を形成する 3 つの重要な概念、 SOLID 原則クリーン アーキテクチャ(実際には SOLID 原則に由来する)、およびAtomic Designを紹介します。これらの領域について強く感じる場合は、このセクションをスキップできます。

堅固な原則

SOLID は、開発者がスケーラブルで保守可能なモジュラー ソフトウェアを作成する際の指針となる 5 つの設計原則を表す頭字語です。


  • 単一責任の原則 (SRP) : この原則は、クラスが変更する理由は 1 つだけであるべきである、つまり単一の責任を持つべきであると述べています。 SRP に準拠することで、開発者は、より焦点を絞り、保守しやすく、テストしやすいコードを作成できます。


  • オープン/クローズの原則 (OCP) : OCP によると、ソフトウェア エンティティは拡張に対してオープンである必要がありますが、変更に対してはクローズされている必要があります。つまり、開発者は既存のコードを変更せずに新しい機能を追加できるため、バグが発生するリスクが軽減されます。


  • Liskov Substitution Principle (LSP) : LSP は、派生クラスのオブジェクトは、プログラムの正確性に影響を与えることなく、基本クラスのオブジェクトを置き換えることができるべきであると主張します。この原則は、継承とポリモーフィズムの適切な使用を促進します。


  • Interface Segregation Principle (ISP) : ISP は、クライアントが使用しないインターフェイスに依存することを強制されるべきではないことを強調しています。より小さく、より焦点を絞ったインターフェイスを作成することで、開発者はコードの編成と保守性を向上させることができます。


  • 依存性逆転の原則 (DIP) : DIP は、開発者が具体的な実装ではなく抽象化に依存することを奨励します。この原則は、よりモジュール化され、テスト可能で、柔軟なコードベースを促進します。


このトピックをさらに深く掘り下げたい場合は、ぜひお試しください。問題ありません。しかし、今のところ、私が提示したことは、さらに先に進むのに十分です。


この記事に関して、SOLID は何を提供してくれるのでしょうか?

クリーンなアーキテクチャ

Robert C. Martin は、SOLID の原則と、さまざまなアプリケーションの開発における豊富な経験に基づいて、Clean Architecture の概念を提案しました。この概念について議論するとき、その構造を視覚的に表すために下の図がよく参照されます。



したがって、クリーン アーキテクチャは新しい概念ではありません。関数型プログラミングやバックエンド開発など、さまざまなプログラミング パラダイムで広く使用されています。


Lodash などのライブラリや多数のバックエンド フレームワークは、SOLID 原則に根ざしたこのアーキテクチャ アプローチを採用しています。


クリーン アーキテクチャは、関心の分離と、アプリケーション内の独立したテスト可能なレイヤーの作成を強調し、システムの理解、保守、および変更を容易にすることを主な目標としています。


アーキテクチャは、同心円またはレイヤーに編成されています。それぞれが明確な境界、依存関係、および責任を持っています。


  • エンティティ: アプリケーション内のコア ビジネス オブジェクトとルールです。エンティティは通常、ユーザー、製品、注文など、ドメイン内の重要な概念またはデータ構造を表す単純なオブジェクトです。


  • ユース ケース: インタラクターとも呼ばれるユース ケースは、アプリケーション固有のビジネス ルールを定義し、エンティティと外部システム間の相互作用を調整します。ユースケースは、アプリケーションのコア機能を実装する責任があり、外側のレイヤーに依存しない必要があります。


  • インターフェース アダプター: これらのコンポーネントは、内層と外層の間のブリッジとして機能し、ユース ケースと外部システム フォーマットの間でデータを変換します。インターフェース アダプターには、アプリケーションがデータベース、外部 API、および UI フレームワークと対話できるようにするリポジトリ、プレゼンター、およびコントローラーが含まれます。


  • フレームワークとドライバー: この最外層は、データベース、UI フレームワーク、サードパーティ ライブラリなどの外部システムで構成されます。フレームワークとドライバーは、アプリケーションを実行し、内部レイヤーで定義されたインターフェイスを実装するために必要なインフラストラクチャを提供する責任があります。


クリーン アーキテクチャは、外側のレイヤーから内側のレイヤーへの依存関係の流れを促進し、コア ビジネス ロジックが使用される特定のテクノロジやフレームワークから独立したままであることを保証します。


これにより、変化する要件やテクノロジー スタックに簡単に適応できる、柔軟で保守可能でテスト可能なコードベースが実現します。

アトミックデザイン

Atomic Design は、インターフェイスを最も基本的な要素に分解し、それらをより複雑な構造に再構築することによって、UI コンポーネントを編成する方法論です。 Brad Frost は、2008 年に「Atomic Design Methodology」というタイトルの記事でこの概念を初めて紹介しました。


以下は、Atomic Design の概念を示す図です。



これは、5 つの異なるレベルで構成されています。


  • Atoms : ボタン、入力、ラベルなど、インターフェイスの分割できない最小単位。


  • 分子: 一緒に機能するアトムのグループで、フォームやナビゲーション バーなどのより複雑な UI コンポーネントを形成します。


  • 生物: ヘッダーやフッターなど、インターフェイスの個別のセクションを作成する分子と原子の組み合わせ。


  • テンプレート: ページのレイアウトと構造を表し、有機体、分子、および原子を配置するためのスケルトンを提供します。


  • ページ: 実際のコンテンツで満たされたテンプレートのインスタンスで、最終的なインターフェイスを示します。


Atomic Design を採用することで、開発者はモジュール性、再利用性、UI コンポーネントの明確な構造など、いくつかの利点を得ることができます。これは、Design System のアプローチに従う必要があるためですが、これはこの記事のトピックではないので、次に進みます。

ケーススタディ: NotionLingo

フロントエンド開発のためのクリーン アーキテクチャに関する十分な情報に基づいた視点を開発するために、私はアプリケーションを作成する旅に乗り出しました。 6 か月間、このプロジェクトに取り組みながら貴重な洞察と経験を得ることができました。


そのため、この記事全体で提供されている例は、アプリケーションでの実地経験に基づいています。透明性を維持するために、すべての例は公開されているコードから派生しています。


次のリポジトリにアクセスして、最終結果を調べることができます。 https://github.com/Levofron/NotionLingo .

クリーンなアーキテクチャの実装

前述のように、オンラインで入手できるクリーン アーキテクチャの実装は多数あります。ただし、これらの実装全体でいくつかの共通要素を特定できます。


  • ドメイン層: アプリケーションのコアであり、ビジネス関連のモデル、ユース ケース、および運用を網羅しています。


  • API レイヤー: ブラウザー API とのやり取りを担当します。


  • リポジトリ レイヤー: ドメインと API レイヤーの間のブリッジとして機能し、API タイプをドメイン タイプにマップするためのスペースを提供します。


  • UI レイヤー: コンポーネントを収容し、ユーザー インターフェイスを形成します。


これらの共通点を理解することで、クリーン アーキテクチャの基本構造を理解し、特定のニーズに適応させることができます。

ドメイン

アプリケーションのコア部分には以下が含まれます。


  • ユース ケース: ユース ケースでは、データの保存、更新、フェッチなど、さまざまな操作のビジネス ルールについて説明します。たとえば、ユース ケースには、Notion から単語のリストをフェッチすることや、学習した単語に対するユーザーの毎日の連続記録を増やすことが含まれる場合があります。


    基本的に、ユースケースは、ビジネスの観点からアプリケーションのタスクとプロセスを処理し、システムが目的に応じて機能するようにします。


  • モデル: モデルは、アプリケーション内のビジネス エンティティを表します。これらは、TypeScript インターフェイスを使用して定義できるため、ニーズやビジネス要件に確実に合わせることができます。


    たとえば、ユースケースで Notion から単語のリストをフェッチする場合、適切なビジネス ルールと制約に従って、そのリストのデータ構造を正確に記述するモデルが必要になります。


  • 操作: 特定のタスクをユース ケースとして定義することが現実的でない場合や、ドメインの複数の部分で使用できる再利用可能な機能を作成したい場合があります。たとえば、Notion の単語を名前で検索する関数を作成する必要がある場合、そのような操作はここに配置する必要があります。


    操作は、アプリケーション内のさまざまなコンテキストで共有および利用できるドメイン固有のロジックをカプセル化するのに役立ちます。


  • リポジトリ インターフェイス: ユース ケースには、データにアクセスする手段が必要です。依存性逆転の原則に従って、ドメイン層は他の層に依存すべきではありません (他の層はそれに依存しています)。したがって、この層はリポジトリのインターフェースを定義します。


    実装の詳細ではなく、インターフェイスを指定することに注意することが重要です。リポジトリ自体は、実際のデータ ソースに依存しないリポジトリ パターンを利用し、それらのソースとの間でデータをフェッチまたは送信するためのロジックを強調します。


    1 つのリポジトリで複数の API を実装でき、1 つのユース ケースで複数のリポジトリを利用できることに注意してください。

API

この層はデータ アクセスを担当し、必要に応じてさまざまなソースと通信できます。フロントエンド アプリケーションを開発していることを考えると、このレイヤーは主にブラウザー API のラッパーとして機能します。


これには、REST、ローカル ストレージ、IndexedDB、音声合成などの API が含まれます。


OpenAPI タイプと HTTP クライアントを生成する場合、API レイヤーはそれらを配置するのに理想的な場所であることに注意することが重要です。このレイヤー内には、次のものがあります。


  • API アダプター: API アダプターは、アプリケーションで使用されるブラウザー API に特化したアダプターです。このコンポーネントは、REST 呼び出しと、アプリのメモリまたは使用するその他のデータ ソースとの通信を管理します。


    必要に応じて、独自のオブジェクト ストレージ システムを作成して実装することもできます。専用の API アダプターを使用することで、さまざまなデータ ソースと対話するための一貫したインターフェイスを維持できるため、必要に応じてデータ ソースを簡単に更新または変更できます。


  • タイプ: これは、API に関連するすべてのタイプの場所です。これらのタイプはドメインに直接関連していませんが、API から受信した生の応答の説明として機能します。次のレイヤーでは、これらの型は適切なマッピングと処理に不可欠です。

リポジトリ

リポジトリ レイヤーは、複数の API の統合を管理し、API 固有の型をドメイン型にマッピングし、データを変換するための操作を組み込むことにより、アプリケーションのアーキテクチャで重要な役割を果たします。


たとえば、音声合成 API をローカル ストレージと組み合わせたい場合、これは最適な場所です。このレイヤーには以下が含まれます。


  • リポジトリの実装: これらは、ドメイン層で宣言されたインターフェイスの具体的な実装です。複数のデータ ソースを操作できるため、アプリケーション内での柔軟性と適応性が確保されます。


  • 操作: これらは、マッパー、トランスフォーマー、またはヘルパーと呼ばれます。この文脈では、操作は適切な用語です。このディレクトリには、未加工の API 応答を対応するドメイン タイプにマッピングする役割を担うすべての関数が含まれており、データがアプリケーション内で使用するために適切に構造化されていることが保証されます。

アダプタ


アダプター層は、これらの層の間の相互作用を調整し、それらを結び付ける役割を果たします。このレイヤーには、以下を担当するモジュールのみが含まれます。


  • 依存関係の注入: アダプター層は、API、リポジトリ、およびドメイン層の間の依存関係を管理します。依存性注入を処理することにより、アダプター層は問題を明確に分離し、効率的なコードの再利用を促進します。


  • モジュールの編成: アダプター レイヤーは、アプリケーションを機能 (ローカル ストレージ、REST、音声合成、Supabase など) に基づいてモジュールに編成します。各モジュールは特定の機能をカプセル化し、アプリケーションにクリーンでモジュール構造を提供します。


  • アクションの作成: アダプター レイヤーは、ドメイン レイヤーのユース ケースを適切なリポジトリと組み合わせてアクションを作成します。これらのアクションは、アプリケーションが下層のレイヤーと対話するためのエントリ ポイントとして機能します。

プレゼンテーション

プレゼンテーション層は、ユーザー インターフェイス (UI) のレンダリングと、アプリケーションとのユーザー インタラクションの処理を担当します。アダプター、ドメイン、および共有レイヤーを活用して、機能的でインタラクティブな UI を作成します。


プレゼンテーション レイヤーは、アトミック デザイン手法を使用してそのコンポーネントを編成し、スケーラブルで保守可能なアプリケーションを実現します。ただし、このレイヤーは、クリーン アーキテクチャの実装に関する主要な主題ではないため、この記事の主な焦点にはなりません。

共有

集中ユーティリティ、構成、共有ロジックなど、すべての共通要素には指定された場所が必要です。ただし、この記事では、この層について深く掘り下げることはしません。


共通コンポーネントがアプリケーション全体でどのように管理および共有されるかを理解するためだけに言及する価値があります。

各レイヤーのテスト戦略

さて、コーディングに入る前に、テストについて議論することが不可欠です。アプリケーションの信頼性と正確性を確保することは非常に重要であり、アーキテクチャの各レイヤーに対して堅牢なテスト戦略を実装することが重要です。


  • ドメイン層: 単体テストは、ドメイン層をテストするための主要な方法です。ドメイン モデル、検証ルール、およびビジネス ロジックのテストに重点を置き、さまざまな条件下で正しく動作することを確認します。テスト駆動開発 (TDD) を採用して、ドメイン モデルの設計を推進し、ビジネス ロジックが適切であることを確認します。


  • API レイヤー: 統合テストを使用して API レイヤーをテストします。これらのテストでは、API が外部サービスと正しく対話し、応答が適切にフォーマットされていることを確認することに重点を置く必要があります。 Jest などの自動テスト フレームワークなどのツールを利用して、API 呼び出しをシミュレートし、応答を検証します。


  • リポジトリ レイヤー: リポジトリ レイヤーでは、単体テストと統合テストを組み合わせて使用できます。単体テストは、個々のリポジトリ メソッドをテストするために使用できますが、統合テストは、リポジトリが API と正しく対話することを確認することに重点を置く必要があります。


  • アダプター層: 単体テストは、アダプター層のテストに適しています。これらのテストでは、アダプターが依存関係を正しく挿入し、レイヤー間のデータ変換を管理することを確認する必要があります。 API レイヤーやリポジトリ レイヤーなどの依存関係をモックすると、テスト中にアダプター レイヤーを分離するのに役立ちます。


アーキテクチャの各レイヤーに包括的なテスト戦略を実装することで、アプリケーションの信頼性、正確性、保守性を確保しながら、開発中にバグが発生する可能性を減らすことができます。


ただし、小さなアプリケーションを構築している場合は、アダプター レイヤーでの統合テストで十分です。

コードを書いてみよう

さて、Clean Architecture についてしっかりと理解して、おそらくそれについて自分の意見を形成したところで、もう少し深く掘り下げて実際のコードを調べてみましょう。


ここでは単純な例のみを紹介することに注意してください。ただし、より詳細な例に興味がある場合は、この記事の冒頭で述べた私の GitHub リポジトリを自由に調べてください。


「実生活」では、クリーン アーキテクチャは大規模なエンタープライズ レベルのアプリケーションで真価を発揮しますが、小規模なプロジェクトではやり過ぎかもしれません。ということで、本題に入りましょう。


私のアプリケーションを例として、API 呼び出しを実行して特定の単語の辞書候補を取得する方法を示します。この特定の API エンドポイントは、2 つの Web サイトを Web スクレイピングして、意味と例のリストを取得します。


ビジネスの観点から、このエンドポイントは、ユーザーが特定の単語を検索できるようにする「単語の検索」ビューにとって重要です。ユーザーが単語を見つけてログインすると、Web スクレイピングされた情報を自分の Notion データベースに追加できます。

フォルダ構造

まず、前に説明したレイヤーを正確に反映するフォルダー構造を確立する必要があります。構造は次のようになります。


 client ├── adapter ├── api ├── domain ├── presentation ├── repository └── shared


クライアント ディレクトリは、多くのプロジェクトで「src」フォルダと同様の目的を果たします。この特定の Next.js プロジェクトでは、フロントエンド フォルダーを「クライアント」、バックエンド フォルダーを「サーバー」と命名する規則を採用しました。


このアプローチにより、アプリケーションの 2 つの主要コンポーネントを明確に区別できます。

サブディレクトリ

プロジェクトに適したフォルダー構造を選択することは、開発プロセスの早い段階で行うべき重要な決定です。リソースの編成に関しては、さまざまな開発者が独自の好みとアプローチを持っています。


リソースをページ名でグループ化する人もいれば、OpenAPI によって生成されたサブディレクトリの命名規則に従う人もいれば、アプリケーションが小さすぎてこれらのソリューションのいずれかを保証できないと考える人もいます。


重要なのは、プロジェクトの特定のニーズと規模に最も適した構造を選択すると同時に、明確で保守可能なリソースの編成を維持することです。


私は 3 番目のグループに属しているため、構造は次のようになります。


 client ├── adapter │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── api │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── domain │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ ├── supabase └── repository ├── local-storage ├── rest ├── speech-synthesis └── supabase


この記事では、共有レイヤーとプレゼンテーション レイヤーを省略することにしました。これは、より深く掘り下げたい人は、詳細について私のリポジトリを参照できると信じているからです。それでは、いくつかのコード例を見て、クリーン アーキテクチャをフロントエンド アプリケーションに適用する方法を説明しましょう。

ドメイン定義

私たちの要件を考えてみましょう。ユーザーとして、意味と例を含む提案のリストを受け取りたいです。したがって、単一の辞書の提案は次のようにモデル化できます。


 interface DictionarySuggestion { example: string; meaning: string; }


1 つの辞書の提案について説明したので、Web スクレイピングによって取得された単語が、ユーザーが入力したものとは異なる場合や修正されている場合があることに言及することが重要です。これに対応するために、後で修正したバージョンをアプリで使用します。


したがって、辞書の提案と単語の修正のリストを含むインターフェイスを定義する必要があります。最終的なインターフェースは次のようになります。


 export interface DictionarySuggestions { suggestions: DictionarySuggestion[]; word: string; }


このインターフェイスをエクスポートしているため、 exportキーワードが含まれています。

リポジトリ インターフェイス

モデルができたので、次はそれを使用します。


 import { DictionarySuggestions } from './rest.models'; export interface RestRepository { getDictionarySuggestions: (word: string) => Promise<DictionarySuggestions | null>; }


この時点で、すべてが明確になるはずです。ここでは API についてはまったく説明していないことに注意してください。リポジトリ自体の構造は非常に単純です。各メソッドが特定の型のデータを非同期的に返すいくつかのメソッドを持つオブジェクトだけです。


リポジトリは常にドメイン モデル形式でデータを返すことに注意してください。

使用事例

それでは、ビジネス ルールをユース ケースとして定義しましょう。コードは次のようになります。


 export type GetDictionarySuggestionsUseCaseUseCase = UseCaseWithSingleParamAndPromiseResult< string, DictionarySuggestions | null >; export const getDictionarySuggestionsUseCase = ( restRepository: RestRepository, ): GetDictionarySuggestionsUseCaseUseCase => ({ execute: (word) => restRepository.getDictionarySuggestions(word), });


最初に注意すべきことは、ユースケースを定義するために使用される一般的なタイプのリストです。これを実現するために、ドメイン ディレクトリにuse-cases.types.tsファイルを作成しました。


 domain ├── local-storage ├── rest ├── speech-synthesis ├── supabase └── use-cases.types.ts


これにより、サブディレクトリ間でユース ケースの型を簡単に共有できます。 UseCaseWithSingleParamAndPromiseResultの定義は次のようになります。


 export interface UseCaseWithSingleParamAndPromiseResult<TParam, TResult> { execute: (param: TParam) => Promise<TResult>; }


このアプローチは、ドメイン レイヤー全体でユース ケース タイプの一貫性と再利用性を維持するのに役立ちます。


なぜexecute関数が必要なのか疑問に思われるかもしれません。ここには、実際のユース ケースを返すファクトリがあります。


この設計上の選択は、リポジトリの実装をユース ケース コードで直接参照したくないという事実と、リポジトリをインポートで使用したくないという事実によるものです。このアプローチにより、後で依存性注入を簡単に適用できます。


ファクトリ パターンとexecute機能を使用することで、リポジトリの実装の詳細をユース ケース コードとは別に保持できるため、アプリケーションのモジュール性と保守性が向上します。


このアプローチは、ドメイン層が他の層に依存しない依存関係逆転の原則に従い、異なるリポジトリ実装を交換したり、アプリケーションのアーキテクチャを変更したりする際の柔軟性を高めます。

API 定義

まず、インターフェースを定義しましょう。


 export interface RestApi { getDictionarySuggestions: (word: string) => Promise<AxiosResponse<DictionarySuggestions>>; }


ご覧のとおり、インターフェイスでのこの関数の定義は、リポジトリでの定義とよく似ています。ドメイン タイプは既に応答を記述しているため、同じタイプを再作成する必要はありません。


API が生データを返すことに注意することが重要です。これが、完全なAxiosResponse<DictionarySuggestions>を返す理由です。そうすることで、API レイヤーとドメイン レイヤーを明確に分離し、データの処理と変換をより柔軟に行うことができます。


この API の実装は次のようになります。


 export const getRestApi = (axiosInstance: AxiosInstance): RestApi => ({ getDictionarySuggestions: async (word: string) => { const encodedCurrentDate = encodeURIComponent(word); const response = await axiosInstance.get( `${RestEndpoints.GET_DICTIONARY_SUGGESTIONS}?word=${encodedCurrentDate}`, ); return response; } });


この時点で、物事はより興味深いものになります。議論する最初の重要な側面は、 axiosInstanceの注入です。これにより、コードが非常に柔軟になり、堅実なテストを簡単に作成できます。これは、クエリ パラメーターのエンコードまたは解析を処理する場所でもあります。


ただし、入力文字列のトリミングなど、他のアクションをここで実行することもできます。 axiosInstanceを注入することで、懸念事項を明確に分離し、API 実装がさまざまなシナリオや外部サービスの変更に適応できるようにします。

リポジトリの実装

インターフェイスはすでにドメインによって定義されているため、リポジトリを実装するだけです。したがって、最終的な実装は次のようになります。

 export const getRestRepository = (restApi: RestApi): RestRepository => ({ getDictionarySuggestions: async (word) => { const { data } = await restApi.getDictionarySuggestions(word); if (!data?.suggestions?.length) { return null; } return formatDictionarySuggestions(data); } });


言及すべき重要な側面は、API に関連しています。 getRestRepositoryと、以前に定義したrestApiを渡すことができます。前述のように、テストが容易になるため、これは有利です。簡単にformatDictionarySuggestionsを調べることができます:


 export const formatDictionarySuggestions = ({ suggestions, word, }: DictionarySuggestions): DictionarySuggestions => { const cleanedWord = cleanUpString(word); const cleanedSuggestions = suggestions.map((_suggestion) => { const cleanedMeaning = cleanUpString(_suggestion.meaning); const cleanedExample = cleanUpString(_suggestion.example); return { meaning: cleanedMeaning, example: cleanedExample, }; }); return { word: cleanedWord, suggestions: cleanedSuggestions, }; };


この操作は、ドメインのDictionarySuggestionsモデルを引数として取り、文字列のクリーンアップを実行します。つまり、不要なスペース、改行、タブ、および大文字を削除します。それは非常に簡単で、隠れた複雑さはありません。


注意すべき重要なことは、この時点では、API の実装について心配する必要がないということです。念のために言っておきますが、リポジトリは常にドメイン モデルでデータを返します。そうしないと、依存関係の逆転の原則が破られるため、そうする必要はありません。


そして今のところ、ドメイン層はその外側で定義されたものに依存していません。

アダプター - すべてをまとめましょう

この時点で、すべてが実装され、依存性注入の準備ができているはずです。 rest モジュールの最終的な実装は次のとおりです。


 import { getRestRepository } from '@repository/rest/rest.repository'; import { getRestApi } from '@api/rest/rest.api'; import { getDictionarySuggestionsUseCase } from '@domain/rest/rest.use-cases'; import { axiosInstance } from '@shared/axios.instance'; const restApi = getRestApi(axiosInstance); const restRepository = getRestRepository(restApi); export const restModule = { getDictionarySuggestions: getDictionarySuggestionsUseCase(restRepository).execute, };


それは正しい!私たちは、特定のフレームワークに縛られることなく、クリーン アーキテクチャの原則を実装するプロセスを経てきました。このアプローチにより、コードが適応可能になり、必要に応じてフレームワークやライブラリを簡単に切り替えることができます。


テストに関して言えば、リポジトリをチェックアウトすることは、このアーキテクチャでテストがどのように実装および編成されているかを理解するための優れた方法です。


クリーン アーキテクチャの強固な基盤により、さまざまなシナリオをカバーする包括的なテストを記述して、アプリケーションをより堅牢で信頼性の高いものにすることができます。


実証されているように、クリーン アーキテクチャの原則に従い、懸念事項を分離することで、保守可能、スケーラブル、およびテスト可能なアプリケーション構造が実現します。


このアプローチにより、新しい機能の追加、コードのリファクタリング、プロジェクトでのチームとの作業が最終的に容易になり、アプリケーションの長期的な成功が保証されます。

プレゼンテーション

サンプル アプリケーションでは、React がプレゼンテーション層に使用されます。アダプター ディレクトリには、残りのモジュールとの対話を処理するhooks.tsという追加のファイルがあります。このファイルの内容は次のとおりです。


 import { restModule } from '@adapter/rest/rest.module'; import { useAxios } from '@shared/hooks'; export const useDictionarySuggestions = () => { const { data, error, isLoading, mutate } = useAxios(restModule.getDictionarySuggestions); return { dictionarySuggestions: data, getDictionarySuggestions: mutate, dictionarySuggestionsError: error, isDictionarySuggestionsLoading: isLoading, }; };


この実装により、プレゼンテーション層の操作が非常に簡単になります。 useDictionarySuggestionsフックを使用することにより、プレゼンテーション レイヤーは、データ マッピングの管理や、主要な機能とは関係のないその他の責任について心配する必要がなくなります。


この関心の分離は、クリーン アーキテクチャの原則を維持するのに役立ち、より管理しやすく保守しやすいコードにつながります。

次は何ですか?

何よりもまず、提供されている GitHub リポジトリからコードに飛び込んで、その構造を調べることをお勧めします。


他に何ができますか?空は限界です!それはすべて、特定の設計ニーズに依存します。たとえば、データ ストア (Redux、MobX、またはカスタムのものでも構いません) を組み込むことで、データ レイヤーを実装することを検討できます。


または、RxJS を使用してバックエンドとの非同期通信を処理するなど、レイヤー間のさまざまな通信方法を試すこともできます。これには、ポーリング、プッシュ通知、またはソケット (基本的には、あらゆるデータ ソースに対応する準備) が含まれる可能性があります。


基本的に、階層化されたアーキテクチャを維持し、逆依存の原則を順守している限り、自由に探索して実験してください。ドメインが設計の中心にあることを常に確認してください。


そうすることで、さまざまなシナリオや要件に適応できる、柔軟で保守しやすいアプリケーション構造を作成できます。

まとめ

この記事では、React を使用して構築された言語学習アプリケーションのコンテキスト内で、クリーン アーキテクチャの概念を掘り下げました。


階層化されたアーキテクチャを維持し、逆依存の原則を順守することの重要性と、関心を分離することの利点を強調しました。


クリーン アーキテクチャの大きな利点は、特定のフレームワークに縛られることなく、アプリケーションのエンジニアリング面に集中できることです。この柔軟性により、アプリケーションをさまざまなシナリオや要件に適応させることができます。


ただし、このアプローチにはいくつかの欠点があります。場合によっては、厳密なアーキテクチャ パターンに従うと、ボイラープレート コードが増えたり、プロジェクト構造が複雑になったりする可能性があります。


さらに、ドキュメントへの依存を減らすことは、長所と短所の両方になる可能性があります。これにより、自由と創造性が向上しますが、チーム メンバー間の混乱や誤解が生じる可能性もあります。


これらの潜在的な課題にもかかわらず、クリーン アーキテクチャの実装は、特に広く受け入れられているアーキテクチャ パターンが存在しない React のコンテキストでは、非常に有益です。


何年にもわたる苦労の後でアーキテクチャに対処するのではなく、プロジェクトの開始時にアーキテクチャを検討することが不可欠です。


実際のクリーン アーキテクチャの例を調べるには、次の URL にある私のリポジトリを自由にチェックしてください。 https://github.com/Levofron/NotionLingo .私のプロフィールにあるリンクから、ソーシャル メディアで私とつながることもできます。


うわー、これはおそらく私が今まで書いた中で最も長い記事です。それは信じられないほど感じます!