クリーンアーキテクチャについてわかったこと(前半)
クリーンアーキテクチャについて
「ソフトウェア」とはどういうものでしょうか?
その一つの答えは「ソフト」、つまり柔軟性、拡張性が高く、変更が容易である機械であるということです。
本稿の目的
この記事の目的は、クリーンアーキテクチャとはどんなものか、簡単に理解できている状態を目指すことです。
「今まで聞いたことはあるけど、実際にどんな考え方かは知らない」「スパゲッティコードになってしまっている状況をなんとかしたい」と思われている方々にぜひ読んでいただきたいです。
内容
保守容易性
変更が容易
変更が容易であることによって、開発コストはかなり少なくなります。
開発の際に、柔軟性が高く、モジュール化もされているコードを想像してみてください。
それらを扱うのは、スパゲッティのように絡み合ったコードを読み解くことと比べて断然早く読み解くことができます。
保守が容易
変更と同様に、保守が容易にできると、開発コストは下がります。
保守が容易であるということは、予期せぬバグが埋め込まれている可能性が低いとも言えます。
新機能のリリースを予定しているのに、従来から存在していた古参のバグの退治に時間を取られて新機能の開発に手をつけられていない開発者も読者の中にはいるかもしれません。
テストが容易
保守が容易なソフトウェアは、往々にしてテストも容易になります。
それは、小さくコンポーネント化されたものを組み合わせて構成されているアーキテクチャになるはずだからです。
小さく細かな機能ごとに分かれているコンポーネントは、巨大な機能を実装しているものと比べてテストしやすいのはおそらく共通認識だと思います。
長期的に見ると、保守がしやすいソフトウェアの方が、保守がしにくいソフトウェアより拡張性が高く、柔軟なために機能数を着実に伸ばしていることがわかります。
保守容易性を考えるために意識すること
- コードの重複を見つけたらDRY(Don't Repeat Yourself)を心がける
- 依存性の注入でテストを容易にする
- ビルダーを導入してオブジェクトの生成を簡潔にする
従来のアーキテクチャの問題点
従来のアーキテクチャでは何が問題なのでしょうか?
従来は、Web層・ドメイン層・永続化層に分けて開発することが一般的で、適切に運用ができていれば、さまざまな変更や機能追加に耐えうるものでした。
しかし、従来のアーキテクチャでは、変更の際に不適切な依存関係が樹立してしまっていてもそれを受け入れてしまっていたため、時間が経つにつれてゴミが溜まってしまうという問題がありました。
このゴミを溜め込まないために、開発者に対して、規律を遵守することや勤勉であることを求めても、人間である以上、100%守れるわけではありません。
データベース中心設計の問題点
従来のアーキテクチャでは、Web層がドメイン層に依存し、ドメイン層が永続化層に依存している構造でした。
これは全てのものが永続化層に依存していることになるため、問題が起きやすい構造です。
また、開発者が本当に考えなければならないのは何でしょうか?
私たちは実現したい状態を操作するために「振る舞い」を制御する必要があります。
「振る舞い」を制御するビジネスロジックを中心に考える必要があるのです。
そのため、従来のようなデータベース(状態)を先に定義するのではなく、ビジネスロジック(振る舞い)を定義する方が理にかなっているのです。
短絡的な実装の問題点
納期が迫ってきて、保守容易性の高い実装から短絡的な実装へと気を緩めてしまう開発者も少なくありません。
私自身もそういう判断になってしまうこともあると思います。
ですが、一度ルールを破ってしまうと、割れ窓理論のように他の開発の場面で同じような短絡的な実装が生まれる可能性も高くなります。
テストがしづらいという問題点
従来のアーキテクチャだとWeb層からドメイン層を飛び越えて永続化層に直接アクセスできてしまう実装になってしまうことがあります。
確かに同じ機能を実装したように見えますが、頻繁にこのような開発が行われていたらどうなるでしょうか?
コードの中にビジネスロジックが散在するようになり、コードが冗長化することが考えられます。
このコードの複雑さが、テストに時間がかかってしまうという問題点に繋がり、結果的にテストがされずに開発が進むという問題点へと発展することになります。
ユースケースが見つけづらくなる問題点
私たち開発者にとって、新しい機能を追加したり、既存のコードの不具合にすぐに気づくことができることは生産性が上がったように感じるし、無駄な時間を過ごしているような感じがしなくてとても開発者体験が良いことだと思います。
しかし、従来のアーキテクチャだと、さまざまなところにドメインロジックが埋め込まれている可能性が高いため、そのような開発者体験の良い開発はしにくくなってしまっています。
異なる作業を同時に行うことが難しくなるという問題点
設計の中心がデータベースである従来のアーキテクチャでは、永続化に関するロジックとビジネスロジックが混ざってしまい、個別の操作が難しくなります。
こういった場合、複数の開発者が同時に作業を行うことでマージ競合が発生し、場合によってはリグレッションせざるを得なくなります。
依存性の逆転とは
単一責任の原則
- コンポーネントは1つのことだけを行うべきであり、そのことを正しく行わなくてはならない。
読者の中にはすでに聞いたことがある人もいるかもしれません。
しかし、本質としては以下のことを覚えておく必要があります。
- コンポーネントを変更する理由は、1つだけになるようにする
単一責任の「責任」に関して、「1機能1つの行動」として理解していた人も多いかもしれません。
しかし、実際のところの単一責任とは、コンポーネントを変更するときの理由が1つである必要がある、ということを指しています。
では、このことがアーキテクチャにとってどのような意味を持っているのでしょうか?
これは、コンポーネントにおいて、変更するところが1つだけなのであれば、他のコンポーネントで例え変更が起こったとしても、その変更を考慮する必要がない、というメリットを持っていることを示します。
上の図で言うと、Dはどこのコンポーネントからも依存されていない状態にあり、ABCは何かしらに依存されている状態になります。
この状態では、Dは変更が容易で柔軟がある一方、ABCはなんらかの変更を行うとその依存先の変更も必要になる場合があるため注意しなければなりません。
時間と共に依存関係は複雑になるため、1つのコンポーネントを変更したときに出る影響が大きくなってしまいます。
依存関係逆転の法則
- コードベース内の依存関係は、いかなるものであっても、その方向をひっくり返す(逆転する)ことができる
例を見ましょう
-
良くないアーキテクチャの例
- ドメイン層を永続化層に密結合させてしまうことにつながる
- ドメイン層を永続化層に密結合させてしまうことにつながる
-
依存関係逆転をしたアーキテクチャの例
行ったこととしては、、永続化層にあるエンティティをドメイン層にうつすことです。
エンティティはドメインに関することを表現しており、ドメイン層のコードは、エンティティの状態を変えることを中心に開発が進むようになっています。
ここで、リポジトリをインターフェースをドメイン層に作成します。
リポジトリのインターフェースからドメイン層のエンティティに直接依存関係が繋がっており、ドメイン層が永続化層に依存している関係だったのが、インターフェースの導入によって、永続化層がドメイン層に依存するように変化しています。
このようにすることでドメイン層のロジックが永続化層から解放されることになります。
パッケージ構成
機能を意識したパッケージ構成にする
機能が関連しているコードは全て同じパッケージに集約するようにします。
そして、新しい機能が追加されたときは、以前のパッケージと同じ階層に新しいパッケージを作成します。
また、同じパッケージ内でしか使用されないクラスはパッケージプライベートで宣言するようにします。
そうすることで、機能をパッケージで明確に区切ることができ、外部からクラスを隠せることで、関連のない機能なのにそれらが誤って依存することを防ぐことができます。
また、パッケージ名やクラス名からアプリケーションの機能がわかるようにすることを「叫ぶアーキテクチャ」と呼ばれています。
この意味としては、アーキテクチャから、その意図を他の開発者に伝えることができる、という意味です。
ユースケースの実装
ドメインモデルの実装
ユースケースの実装を簡単に見ていくことにします。
ユースケースをオブジェクト指向的にモデリングするときは、タスクを表すTaskクラスとそのクラスに対して、タスクを登録するregisterメソッドと削除するdeleteメソッドを定義することが考えられます。
ユースケースの概要
通常のユースケースでは以下の手順で処理を進めます。
- 入力値を受けとる
- ビジネスルールに関する妥当性確認を行う
- ドメインモデルの状態を変える
- 処理結果を変えす
ここでは、入力値のチェックをあえて行っていません。
その理由はユースケースのコードはドメインロジックのみに目を向けるべきものだからです。
入力値の妥当性をユースケースのコード内で行ってしまうと、ユースケースのコードを汚してしまうことになりかねません。
そのため、別のところで入力値チェックは行うようにします。
しかし、ビジネスルールに関する妥当性確認はしっかり行うようにしましょう。
入力値の妥当性確認に関して
入力値の妥当性確認はユースケースの責務ではなく、アプリケーションの責務としています。
入力値をさまざまなところで受け取っていたり、妥当性確認を行なったり行わなかったりすると不正な値が持ち込まれることになりかねません。
そのため、入力モデルの段階で入力値の妥当性確認をすることで不正な値が入ってくるリスクを下げることができます。
参考資料
- 「手を動かしてわかるクリーンアーキテクチャ」,Tom Hombers 著/須田智之 訳.
Discussion