ソフトウェアアーキテクチャが必要かどうか
- 寿命によって決まる
- ワンタイムのプログラムは、不要
- 1ヶ月続けるサービスは、何かしらのソフトウェアアーキテクチャを採用すべき
- 本当に1ヶ月で終わるのか予測はつかないのでやっておいて損はない
- サービスの長期化に備えておくべき
- ソフトウェアアーキテクチャは長く運用するときに採用するべき
- 内部品質に欠けた損益分岐点が1ヶ月
どのソフトウェアアーキテクチャを選択するか
- 「抽象化と問題の分割によって複雑性を減らしてコードをシンプルに保つ」ことを目的としている
- 抽象化:注目すべき要素を重点的に抜き出す、抽象化の度合いを揃える=重要かつ容易
- 問題の分割:人間はマルチタスクは苦手なので、問題の分割によって複雑性を減らす
- シンプルに保つ:
- → Easy:主観的
- → Simple:客観的
- → 「難しいけどシンプル」は成り立つ(絡まり合ったコードを綺麗に保つ、絡まりづらいコードを保つための仕掛けをソフトウェアアーキテクチャが提供する)
レイヤードアーキテクチャ
- レイヤーごとに配置すべきモジュールを分けるという趣旨のアーキテクチャ
- モジュールの目的ごとに配置
- インフラストラクチャ
- データストアからのデータの呼び出し
- ドメイン
- ソフトウェア化の対象領域のこと
- ドメインに含まれるものやことをコードで表現したもの=ドメインオブジェクト
- ドメインオブジェクト=ドメインにおけるルールが記載されたオブジェクト
- アプリケーション
- ドメインオブジェクトを協調させて達成したいことをこなすコード
- ゴールに導く
- ユーザーインターフェース
- アプリケーションを呼び出すコード
- 上位レイヤーは下位レイヤーに依存して良い、その逆はダメ
- ドメインオブジェクトがインフラオブジェクトに依存しても良いと思ってしまうが、システムの方針の主導権を握るのがインフラストラクチャオブジェクトになってしまうので NG。システムにおいて重要なのは、アプリケーションオブジェクトかドメインオブジェクトなので、ドメインオブジェクトがインフラストラクチャオブジェクトに依存しないようにしないといけない
- ユーザーインターフェースオブジェクトがインフラストラクチャオブジェクトに触れることができるが、本来ドメインオブジェクトに記述されるべきことが、重要なルールが点でさまざまな場所に分散されて修正が大変になり、コード全体をみないと全体を理解できなくなってしまうため NG
- スマートUI というアンチパターンになってしまう
- インフラストラクチャ
ヘキサゴナルアーキテクチャ
- モジュールのインターフェースが一致していれば、実態(オブジェクト)が変わっても問題なく動作するということ
- ルールが最小限なので、実装するときに悩みが生じやすい
- ポート・アダプタが重要なアーキテクチャ
- Primary
- アプリケーションを呼び出すモジュール(アダプタ)
- アプリケーションのメソッド(ポート)
- 1つのプライマリポートに対して、複数のプライマリアダプタという関係
- ex)
- userService.addUserStory を BackLogController から呼び出す
- userService.addUserStory を testAddUserStory(Unit test) から呼び出す
- Secondary
- アプリケーションから呼び出すインターフェース(ポート)
- インターフェースの実装モジュール(アダプタ)
- ビジネスロジックがアプリケーションに記述され スマート UI が起きなくなり、かつビジネスロジックがデータストアなどの技術詳細に侵食されなくなる。結果としてテストが可能になる
- 具象クラスをどうやって渡すのか?
- IoC Container:どのインターフェースにどの具象クラスを渡すのかを設定する
- ServiceLocator:そのモジュールが何を必要とするのかわかりづらくなってしま
- @AutoWire:そのモジュールが何を必要とするのかわかりづらくなってしまう
- デメリット
- 本来必要なモジュールを設定し忘れるという設定エラーが実行するまでわからず知らず知らずのうちにテストを壊してしまう
- 依存関係にあるモジュールがコードを実際に確認しないとわからなくなってしまう
オニオンアーキテクチャ
- レイヤー = ディレクトリではなく、4つのカテゴリの存在とそれぞれの依存の方向に言及したアーキテクチャ
- ヘキサゴナルアーキテクチャと同じ
- ヘキサゴナルアーキテクチャのアプリケーション部分を詳しく説明しているアーキテクチャ
- 依存の方向は全て内向き(外側のモジュールが内側のモジュールに依存するのは OK)
- 依存する先はインターフェース、実際に動作するのは具象クラス
- Domain Service
- リポジトリのインターフェース
- リポジトリ:データの永続化を担うオブジェクト(ドメインにはデータの永続化という概念が存在しないので、ドメインで持つべきではない。データの永続化をリポジトリが担うことで、ドメインはドメインの表現に集中できるようになる)
- リポジトリの実装クラスはインフラストラクチャなので、一番外側
- 印象的な言葉
- 3年ごとにデータアクセス技術が変わる
- インフラストラクチャに依存させない
- インフラストラクチャとは、アプリケーションに競争上の優位性を与えない汎用的なコードのこと
クリーンアーキテクチャ
- 基本的にルールは他と同じ
- アプリケーションにとって重要なものを中心に配置して、依存の方向を外から内に向ける
- Enterprise Business Rules (Entities):ビジネスルールをカプセル化したオブジェクト(ドメインオブジェクト)
- Application Bussiness Rules (UseCases):システムのユースケース(レイヤードアーキテクチャのアプリケーションレイヤーと同じもの)
- Interface Adapters (Gateways, Presenters, Controllers):変換を扱う。ユーザーの入力をアプリケーションが必要とするフォーマットに変換したり、Entities・UseCase に便利な形式をデータの永続化に便利な形式に変換する。MVC アーキテクチャがここに内包される
- Frameworks & Drivers (Web, UI, External Interfaces, DB, Devices):内側に対し悪影響を及ぼさないように外側に保つ
- 外側が内側に依存するように。逆は NG
- それぞれの目的に従ってオブジェクトが細分化されているので、このコードはどこに書くべきか?という本質的な悩みが生まれなくなるのはメリット
- あまりに窮屈なルールは最終的に守られなくなってしまうので、何かしらの対策が必要
- 自動生成するツールを作るなど
ADOP
- とにかくやってみよう。はじめやすいアーキテクチャ。成瀬さんが考えたもの
- Domain:リポジトリのインターフェースやファクトリもここに配置する
- ここに配置する理由
- ドメインオブジェクトの近くに、リポジトリのインターフェースやファクトリがないとコンストラクタ以外に再構築や生成の手段が用意されていることに気がつかないために、同じレイヤーに所属させるべき
- Application:アプリケーションサービスといったオブジェクトを配置する。1クラス1メソッドではない。比較的大きな粒度で分割する。1つのテーマごとに分割する
- Others:ドメインでもアプリケーションでもないものはここに配置する
- 上から下への依存は OK
- ディレクトリ構成の指針がある
- 1つのファイルに複数のクラスがあると可読性が下がる
- Domain:リポジトリのインターフェースやファクトリもここに配置する
考慮事項
- サービスの寿命
- 人(習熟度)