なぜソフトウェアアーキテクチャを考えるか
はじめに
ソフトウェアアーキテクチャについて考えたり、話したりするときに、具体的に何のアーキテクチャを使うか?みたいな話になりがちな気がしている。
それは、ソフトウェアアーキテクチャについての情報が多く提供され、多くの人が認知した結果だと思っており、基本的にはいいことだと思う。
ただ、例えば『クリーンアーキテクチャ』で作る。と言ったときに、今作っているソフトウェアは本当に『クリーンアーキテクチャ』なのか?というような話に遭遇することがあり、それはなんだか不毛じゃないか?と思う。
『クリーンアーキテクチャ』みたいな一つのまとまった提案は、とても参考になるし参考にすべきだと思うが、そもそも問題を解決するための手段としてアーキテクチャを持ち出すのに、その問題を解決できているか?で議論を行わないのは良くない状態だと思う。
本来は、例えば『クリーンアーキテクチャ』にどのような問題を解決するポテンシャルがあるか、その解決するための手段としてどのような方法が取られているかを理解し、必要に応じて変更し利用していくことが必要だと思う。
これを考えるためには、そもそも私達がソフトウェアアーキテクチャというものを求める理由を理解して、どのようにその効果が得られるかを理解することが必要だと思う。
ということで、特定のソフトウエアアーキテクチャを学ぶ前の段階として、なぜソフトウェアアーキテクチャを考えるか。について私の考えを書こうと思う。
ソフトウェアアーキテクチャとは何なのか
Wikipedia になんて書いてあるかを見てみた。
色々書いてあるが以下の記述がしっくり来た。
ソフトウェアアーキテクチャとは、抽象化と問題の分割によって複雑性を減らすことを主に念頭に置いたものである。ただし、今までのところ、「ソフトウェアアーキテクチャ」という用語に関して、万人が合意した厳密な定義は存在しない[4]。
ソフトウェアは複雑であるから、複雑性を減らしたいという欲求がある。
ソフトウェアアーキテクチャはこれを解決するために利用するものだと考える。
したがって、既にいくつか存在するソフトウェアアーキテクチャはソフトウェアの複雑性を減らす手段であると捉えることにする。
複雑性軽減のアプローチ
ソフトウェア開発では問題を比較的単純なものの集合に再構成することで複雑さを軽減できる。いわゆる分割統治だ。
つまり、ソフトウェアアーキテクチャを利用することで、ソフトウェアをいくつかのまとまりに分割し、ソフトウェア全体の開発をいくつかの小さなソフトウェアの開発にできる。
多くのソフトウェアアーキテクチャでは、関心に合わせてソフトウェアモジュールを分割することが提案されている。
なにかの観点でソフトウェア開発を小問題に分割することで複雑性を軽減しようとしていることがわかる。
問題を小さくすると、それぞれの問題は比較的簡単に解ける。
一方で、それぞれの問題を再編して元々解きたかった問題を解く必要が発生する。
分割した問題がそれぞれ自由に関係を持っていると結果として全体の複雑性が増してしまうので、関係性の制御も同時に行う必要がある。
多くのソフトウエアアーキテクチャでは、分割したソフトウエアモジュールの依存関係や制御関係を定義している。
分割された小問題によって問題が逆に複雑化することを防ごうとしていることがわかる。
したがってソフトウェアアーキテクチャでは、複雑性軽減ために、”問題の分割”と”関係性の制御”を主に行っていると考える。
問題の分割
以降ではソフトウェア開発を小さなソフトウェア開発に分割する際の観点について考える。
各観点に対していくつかのソフトウェアアーキテクチャを例示しながらどこに注目しているかを考える。
問題をいくつに分けるべきか?という点があると思うが、ソフトウェアの規模とその他の観点によって決まるものだと思う。(例えば、小さなソフトウェアは細かく分けなくても問題解決には十分)
ここで大事なのは、どのような分け方が存在しているかという点で、自分が問題を分けようと思ったときにどのような観点で分けられるかを理解することだと思う。
アプリケーションの機能に着目する
機能に注目してソフトウェアを分けようとするのはとても自然といえる。
MVC
MVC はアプリケーションの役割を以下の3つに分けて考えている
- Model
- データ処理
- View
- 表示
- Controller
- 入力処理
問題領域に着目する
例えば、機能的にはデータの処理を行うものでも、実装対象特有のロジックやデータベースへの永続化処理など、いくつかの問題領域が混在していることがある。
このように単にアプリケーションとしての機能ではなく、アプリケーションが有する問題領域に着目して分割することがある。
オニオンアーキテクチャ
ここでは、オニオンアーキテクチャを考える。
ここでは以下の5つに分けている (テストを除いている)
- UI
- ユーザとのインタラクション
- インフラストラクチャ
- データの永続化や別サービスの呼び出しなどシステム的に必要な (比較的) 低レベルな機能を実装する
- アプリケーションサービス
- アプリケーション固有のロジック実装する
- ドメインサービス
- 複数の問題領域を扱う業務ロジックを実装する
- ドメインモデル
- 特定の問題領域をモデリング・実装する
MVCがアプリケーションの機能に注目して問題を分割しているのに対して、オニオンアーキテクチャではアプリケーションに必要な問題領域に注目して問題を分割している。
例えば、あるWebアプリケーションを実現するためには、その業務自体を表現するという問題領域があるし、Webアプリケーションそのものに関するロジックという問題領域がある。
結果的にMVCをより細かくしたように見えるかもしれないが、どちらかというとこれはアプリケーションの入出力やデータ処理などの機能とは違った切り口をした (観点が違う) ものだと考えている。
関係性の制御
いくつかの役割に基づいてソフトウェア分割した。
一方で、小さくなったソフトウェアたちが密に関係をしていたらそれは複雑性が解決したとは言えない。むしろ複雑性が増したと感じるだろう。
したがって、小さく分割された問題の関係性が整理されていないといけない。
MVC
それぞれの区分の関係を示すと以下のよう感じになる。
View
↑
Controller
↓
Model
オニオンアーキテクチャ
オニオンアーキテクチャでは以下のような依存関係になるようにする。
UI / インフラストラクチャ
↓
アプリケーションサービス
↓
ドメインサービス
↓
ドメインモデル
例えば、業務に関する問題領域は他の何にも依存しない。
業務アプリケーションが Web で実装されても、コマンドラインで実装されてもそれによってドメインモデルの実装は変わらない。
データベースがMySQLであろうと PostgreSQL でもドメインモデルの実装は変わらない。
具体的な例
TODO アプリケーションを作ると考えてみよう。
ここでは以下のような仕様があるものとする。
- コマンドラインで動作する
-
view
サブコマンドで TODO 一覧が表示される -
add
サブコマンドで TODO を追加できる。追加後に TODO 一覧が表示される -
done
サブコマンドで TODO を完了できる。完了後に TODO 一覧が表示される -
delete
サブコマンドで完了した TODO を削除できる。削除後に TODO 一覧が表示される - TODO のデータはテキストで
~/.todo
ファイルに保存される
既存のアーキテクチャに当てはめる
これを先程出てきた MVC と オニオンアーキテクチャ に当てはめてみるとどのようになるだろうか。
MVC
- Model
- TODOデータをもとにTODOデータファイルへの書き込み
- TODOデータファイルから読み込みTODOデータへの変換
- TODOデータの編集
- 追加
- 完了
- 削除
- View
- TODOデータから読みやすいテキスト表示への変換
- Controller
- ユーザから受け付けたコマンドに基づいて必要な Model の処理を呼び出す
- Model の処理結果を View に渡す
- View の結果をユーザに返す
前述の通り、ユーザからの入出力、データ処理、表示にアプリケーション全体の問題が別れていることがわかる。
オニオンアーキテクチャ
- UI
- TODOデータから読みやすいテキスト表示への変換
- インフラストラクチャ
- TODOデータをもとにTODOデータファイルへの書き込み
- TODOデータファイルから読み込みTODOデータへの変換
- アプリケーションサービス
- コマンドをもとに適切なドメインサービスを呼び出す
- ドメインサービスの処理結果をもとにUIを返す
- ドメインサービス
- 削除や追加などの処理をドメインモデルを利用して実施し、結果を返す
- ドメインモデル
- TODOに関するデータ・振る舞い
MVCよりも多くの問題に分割しているためより細かくなっている。
また、問題の分け方も異なっていることがわかる。
気をつけたいのは、細かく別れているからいいというわけではないという点だ。
はじめに記載したとおり、問題が細かく別れているということは各問題の関係性を適切に管理する必要があるからだ。
取り組む問題の大きさ、複雑性、現状把握できる将来の問題を踏まえて適切に検討をする必要がある。
要件を追加する
アーキテクチャによって問題が小問題へと分割され複雑性が低減した。
これによるメリットがいくつかあるが、大きなメリットとして挙げられるのは、変更が必要になったときに、解くべき対象が元々のソフトウェア全体という大きな問題ではなく、分割された小問題になるということだ。
これはソフトウェアをソフトウェアたらしめる変更しやすさを維持するために必要だ。これはよく柔軟性と表現されることがある。
ここでは、現在は CLI アプリケーションとして実装していたものが Web アプリケーションとして実装されるとしよう。また、永続化はファイルではなく SQLite データベースで行う
MVC
- Model
- 変更: TODOデータをもとにSQLiteデータベースへの書き込み
- 変更: SQLiteデータベースからTODOデータへの変換
- TODOデータの編集
- 追加
- 完了
- 削除
- View
- 変更: TODOデータからHTMLテキストへの変換
- Controller
- 変更: ユーザから受け付けたHTTPリクエストに基づいて必要な Model の処理を呼び出す
- Model の処理結果を View に渡す
- View の結果をユーザに返す
表示・処理・入力形式が変更になったのでそれぞれのレイヤで変更が発生している。
一方で領域でどのような変更すべきかが明確に分かれている。
アーキテクチャによって、変更全体をそれぞれの小問題として扱うことができることがわかる。
オニオンアーキテクチャ
- UI
- 変更: TODOデータからHTMLテキストへの変換
- インフラストラクチャ
- 変更: TODOデータをもとにSQLiteデータベースへの書き込み
- 変更: SQLiteデータベースから読み込みTODOデータへの変換
- アプリケーションサービス
- 変更: HTTPリクエストをもとに適切なドメインサービスを呼び出す
- ドメインサービスの処理結果をもとにUIを返す
- ドメインサービス
- 削除や追加などの処理をドメインモデルを利用して実現する
- ドメインモデル
- TODOに関するデータ・振る舞いを実装する
コマンドラインからWebアプリケーションになったことで、アプリケーションの各領域で変更が必要になった。
- 表示 (テキスト -> HTML)
- 永続化方法 (テキストファイル -> SQLite)
- アプリケーションの入出力インターフェース (コマンドライン -> HTTP)
重要なのは関係性が適切に制御されているため、直接的・間接的に関わらずこの変更はドメインサービスとドメインモデルに影響しないということだ。
変更を簡単に行うことができ、柔軟なソフトウェアであると言えるだろう。
終わりに
はじめに、ソフトウェアアーキテクチャを利用する目的を考えた。
次にその目的を果たすためのアプローチを考え、よく知られている MVC・オニオンアーキテクチャを当てはめて理解しようとした。
最後に TODO アプリケーションの例を用いることで、問題の複雑さが軽減し、結果としてソフトウェアの柔軟性を維持することにアーキテクチャが貢献することを見た。
このあたりの理解があれば、自分のプロダクトに対して適切なアーキテクチャを検討することができると思うし、先のことを見据えつつ、現状はアーキテクチャを簡素なものにするというような選択も必要なら取れると思う。
ソフトウェアは変化するもので、状況によって重要となるものも変わっていくためこのような検討ができることが大事だと思う。
今回は例として MVC と オニオンアーキテクチャを利用した。
例えば、クリーンアーキテクチャではどうなるのかなどを自身で考えてみると良いと思う。
各役割がどのような目的で分離されていて、それによって何が達成されるか、それぞれの関係性がどのように制御されていることでどのような恩恵があるかということを理解できると思う。
また、ここで記載した問題の分割と問題の関係性制御についてはソフトウェアのアーキテクチャに限った話ではない。システムアーキテクチャでも同様な考えが適用される。
トラディショナルな三層アーキテクチャやマイクロサービスなどだ。
システム開発において問題の分割と問題の関係性制御は一般的な解決手段であると言える。
それらをより効果的に利用するためにアーキテクチャのパターンがある。
パターンをただ利用するだけではなく、問題の解決のために活用するというスタンスを取ることが必要だと、はじめに記載したようなシーンで共有できると嬉しい。
Discussion