Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScript ではじめる Clean Architecture / Let's start Clean Architecture with TypeScript

dojineko
October 15, 2019

TypeScript ではじめる Clean Architecture / Let's start Clean Architecture with TypeScript

dojineko

October 15, 2019
Tweet

More Decks by dojineko

Other Decks in Technology

Transcript

  1. 「クリーンアーキテクチャ」とは? • アプリケーションを4層 大枠に分割して、責務と関心 分離を行い 疎結合な設計にするため コンセプト • 「Enterprise Business

    Rules」 ◦ アプリケーションに依らない根本的なビジネスルール 層 • 「Application Business Rules」 ◦ アプリケーション的なビジネスルール 層 • 「Interface Adapters」 ◦ External Interface を可換にするインターフェースとデータ構造体 層 • 「Frameworks and Drivers」 ◦ データベースやキャッシュ、 APIなど 外部リソース 層
  2. クリーンアーキテクチャによる制約 • クリーンアーキテクチャ 原則 依存を一方に統一すること • 依存できる実装 、常に円 中心方向へ み

    ◦ 逆方向へ 依存が発生する場合 interface を使用し、依存関係 逆転 (DIP)を活用する ◦ 「依存関係 逆転(DIP)」: 雑な解釈として 依存を抽象化し初期化時に実装を注入すること • 基本的に層をまたいで実装を使用すること しない ◦ 層をまたいでデータを引き渡す場合 専用型へ 詰め直しが必要になる (Data Structure などと呼 れる)
  3. Enterprise Business Rules • アプリケーションに依らない根本的なビジネスルールを格納する • Entity と呼 れるクラス群が収まる層 •

    同階層 クラス群以外に 依存しない コアなロジックしか書かない で 比較的小さなパーツになるよ
  4. Application Business Rules • アプリケーション的なビジネスルールを格納する • UseCase や Interactor と呼

    れるクラス群が収まる層 • Entity と同階層 実装に依存でき Interface Adapters interface を使用できる • 「依存する」と 言い換えると「実装を直接使用する」こと ◦ 「interface を使用する」こと 「依存していない」ことになる
  5. Interface Adapters • データ 入出力や永続化処理、表示を抽象化する層 • Repository、Gateway、Presenter と呼 れるクラス群が格納される •

    同階層 実装に依存でき Frameworks and Drivers interface を使用できる • 基本的にDIPにより依存関係 逆転した状態となる • ここに 直接永続化したりする処理 基本的に 書かない
  6. Frameworks and Drivers • 外部リソースへ 具体的なアクセス手段を格納する層 • Driver や Infrastructure

    と呼 れるクラス群が格納される • 基本的にDIPにより依存関係 逆転した状態となる • 外部フレームワークやDB ドライバへ 直接 依存ができる ◦ MySQLやPostgreSQL、Redisなど ドライバを直接使用するコード ◦ ORM や Web API フレームワークを直接使用するコード ◦ など
  7. なにがうれしいのか? • 層が意識された設計になることで、アプリ 構成要素が疎結合になる ◦ 構成要素: フレームワーク、データベース、ビジネスロジックなど • アプリケーション テストコードが書きやすくなる

    ◦ 単体テストでデータベースを要求するようなシーンが減る • コード変更 影響を最小限に抑えられ改修コストを下げられる ◦ 責務や関心が分離されている で副作用を出しづらい ◦ 継続的な開発に向いている設計
  8. 具体的なメリットの例は? • 特定 ライブラリを別 なにかに置き換えるときに楽できる ◦ アプリケーション固有 ロジックが、 フレームワークやライブラリから分離されている ことによるメリット。

    ◦ フレームワークやライブラリ側 EOLなど どうにもならないケースや、 後発 より良い実装に移りたいシーンなどにおいてかなり有用。 • 実際にデータベースを用意しなくてもロジックがテストできる ◦ データあるい データ構造とロジックが分離されている ことによるメリット。 ◦ 単体テストを書くときにやってしまいがちな、モックライブラリを多用しながら、 データベースへ 副作用を押し潰しながら実施するようなテストを書かなくて済む。 ◦ 必要であれ ライブラリを使用している層 みをテストしてあげれ 良い。
  9. 実例: ORMライブラリを置き換える例 • ORM を Sequelize から TypeORM に置き換えたい。 ◦

    ライブラリ自体が TypeScript を想定した作りになっているため運用上メリットが大きい。 • Sequelize モデルにアプリケーション コアロジックが、 すでに大量に記載されており、移行 阻害要因になっている。 ◦ また、TypeORM に単にそ まま移行するだけで 、同じことが移行先でも起こる で メンテナンス性をあげるという点において 不十分。 ◦ コアロジックを適切に抽出して、ライブラリに依存しないようにする必要がある。 • 並行してクリーンアーキテクチャに整備していくことで、 全体 メンテナンス性を上げつつ、 他 ライブラリに置き換える際も楽ができるようになる。
  10. デメリットは? • 素直にコードを書くより 圧倒的に記述量が増える ◦ interface や class を細かく分割して疎結合な構成を保つ仕組みな で

    ファイル数や記述量が増えてしまう 致し方なし • コードをどこに書け いい か悩みがち ◦ 一部 キーワードが他 設計方法や フレームワークとかぶっていてややこしい • 「完全に理解する」に 少し時間が必要な場合も ◦ 逆を言うと、触れていれ 理解できるようになる メリット 大きいけど 最初だけちょっと大変 ...
  11. 解決策は? • クラスやファイルが多い問題 ◦ ある程度運用ルールが固まってくれ ジェネレータを作って 雛形生成するなどで対策可能 • コードをどこに書くか問題 ◦

    大枠で層を分割することをまず意識しながらコードを書く ◦ 適切に層に分けられていない場合 テストがしづらいことなどで分かる で適宜見直して再配置する • 「完全に理解した」い問題(?) ◦ コードを書きながらコンセプト図をたまに見返して勘所を得る練習を繰り返す ◦ チーム開発なら 理解を得るためにメリットを説く普及活動も並行すると良い
  12. サンプルコードの説明 • 機能概要 ◦ 登録されているユーザー データを一覧にする ◦ 単一 ユーザー プロフィールを表示する

    • 起動時 指定で可換な機構 ◦ Webアプリフレームワーク : Koa.js or Express.js ▪ ライブラリ 置き換えを想定したサンプル ◦ データストア: 組み込み(Static) or ファイル(JSON) ▪ データベースをロジックから分離した場合 サンプル
  13. Index アプリケーション エントリポイント。 起動に必要な環境変数を確認して機構 (Controller, Repository, Usecase, Driver)を 初期化する。 サンプルで

    、Pure-TypeScript で書いてある で記載が冗長だが InversifyJS など DIコンテナ を活用することでスッキリ 書くことができるようになる。
  14. Driver 外部ライブラリ 利用方法を格納する。 サンプルで 、Webサーバー DriverとしてKoa 版とExpress版 Driverが存在する。これら ServerDriver interface

    を実装する。 同じくデータストア Driverとして、Json版と Static版 Driverも存在する。これら DataStore interface を実装する。 コアロジック Controllerよりも円 内側 コード にかかれており、Driverで Controllerと外部ラ イブラリ 橋渡しをする程度しか処理がない。 ※ コード src/drivers/koa および、 src/drivers/express などを参照 Express版 Koa版
  15. Controller Driverから受け取った値と、自身が持つ UseCase と 連携を取り持つ。 Controller になにをすれ いい かを指示する と、UseCase

    を呼び出して実際 処理が始ま る。 ※ コード src/adapters/controllers を参照 Controllerが使用するUseCase constructorで受け取る Controller 処理に応じた UseCase 実行関数を実行する
  16. Repository データ 取得保存など永続化に関するロジックを 格納する。 Repository interface で DIP を適用する際に 使用できる。

    注意する点 DataStore から得られる型をそ まま外に流出させないように Data Structure へ 詰め直しを行う点。 そ まま型情報を返却すると例え データベー ス 構造にコードが依存してしまう。 また、層による分割 ルールで、DataStore Response が直接 Entity になるようなこと 基 本的にない。 ※ コード src/adapters/repositories を参照 DataStore Response 型を改めて定義する Repository 実装で DataStore から得たデータを 定義した型に合うよう詰める
  17. UseCase アプリケーション固有 ビジネスロジックを格納 する。 実 UseCase interface で実際 UseCase を実装する

    Interactor というクラスが処理を行 う。 UseCase で データストアから、データ 取得を 行う必要があるが、依存関係 方向が逆向きに なってしまうため DIP を適用して、UseCase 初 期化時に Repository を注入している。 ※ コード src/applications/usecases を参照 UseCase自体 interface 実装 Interactor が行う 使用する Repository constructor で受け取る
  18. Entity アプリケーションであることに依存しないコアロ ジックを記載する。 また基本的に、Entityで 外部ライブラリを直接 使用しないように、組み込み型や組み込み関数 など みでコードを記載する。必要応じて一部 ライブラリ 許可するなどしても良い。

    サンプルコードで score に応じて ヘビーユー ザーかどうかを boolean で返す関数を実装して いる。 ※ コード src/domains/entities を参照 Entity コード 基本的に 外部ライブラリに依存しない。 (他 Entityを使用する OK)
  19. ▪ サンプル全体 大枠 構成図 Entity Repository (interface) Gateway UseCase (interface)

    Interactor Controller Server (interface) Koa.js JSON DataStore (interface) Static Express.js
  20. まとめ • クリーンアーキテクチャ アプリケーション設計 コンセプト • フロントエンド、サーバーサイド問わず普遍的に活用できる設計手法 • 大切な 依存

    方向を一方にし、責務と関心を分離すること • 結果としてアプリケーション 実装が疎結合になる • 実装が疎結合になると単体テストがしやすくなり、変更にも強くなる