blog

クリーン・アーキテクチャを私たちのために翻訳する

もしあなたが上級のソフトウェア・エンジニアなら、今すぐ読むのをやめてもかまいません。これは入門レベルの記事です。 コンピュータの本はすぐに内容が古くなってしまうので、私は通常買いません。また、情報はす...

Jun 10, 2020 · 9 min. read
シェア

もしあなたが上級のソフトウェア・エンジニアなら、今すぐ読むのをやめてもかまいません。これは入門レベルの記事です。

序文

コンピュータの本はすぐに内容が古くなってしまうので、普段は買いません。また、情報はすべてオンラインで見つけることができます。しかし1年ほど前、ロバート・マーティンのClean Code』を読み始めました。この本は私のソフトウェア開発のやり方を大きく変えました

本書には理解しにくい部分もありますので、重要なコンセプトを一般の方にも理解できるように要約して説明したいと思います。私はまだ成長途上のソフトウェア・アーキテクトですので、私の書いたものは批判的な目で読んでください。

シンプリシティ・アーキテクチャーとは?

アーキテクチャとは、プロジェクトの全体的な設計を意味します。コードをクラス、ファイル、コンポーネント、モジュールに編成することです。また、これらのコードのグループが互いに関連する方法です。アーキテクチャは、アプリケーションが主要な機能を実行する場所と、これらの機能がデータベースやユーザインタフェースとどのように相互作用するかを決定します。

シンプルなアーキテクチャとは、プロジェクトの構造を理解しやすく、プロジェクトの成長に合わせて変更しやすいように整理することです。これは気軽にできるものではなく、意図的に行う必要があります。

シンプルなアーキテクチャの特徴

大規模でメンテナンスしやすいプロジェクトを構築する秘訣は、ファイルやクラスを独立して変更できるコンポーネントに分けることです。意味を解放するためにイメージのセットを使いましょう。

上の写真で、ハサミの代わりにナイフを使いたい場合は、ペン、インクボトル、テープ、コンパスについている紐をすべてほどいて、ナイフに結び直します。ナイフならそれで済むかもしれませんが、ペンとテープにハサミが必要だったらどうでしょう。ペンとハサミは使えないので交換する必要がありますが、そうすると、ペンとハサミにつながれている他の物にも影響が出ます。

コントラスト:

ハサミを交換するには?ハサミに結ばれているひもをポスト・イットの下から引き抜いて、ナイフに新しいひもをつけるだけです。

つ目のイメージは、変更が格段に容易なアーキテクチャを表しています。ポスト・イットの付箋が頻繁に変更されない限り、このシステムはとても保守しやすいでしょう。同様に、このアーキテクチャは、あなたのソフトウェアの保守と変更を容易にします。

内側の円はアプリケーションのドメインレイヤーで、アプリケーションロジックなどを含みます。外側の円は、UI、データベース、Web API、フレームワークなどを含むインフラストラクチャです。これらの部分はアプリケーションロジックよりも頻繁に変更されます。例えば、ボタンの機能よりもUIボタンのスタイルを変更することの方が多いかもしれません。

アプリケーション・ドメイン層とインフラストラクチャの間に境界を設け、アプリケーション・ドメイン層がインフラストラクチャについて知る必要がないようにします。例えば、UIとデータベースはアプリケーションロジックに依存しますが、アプリケーションロジックはUIとデータベースに依存しないため、プラグインアーキテクチャになります。

アプリケーション・ドメイン・レイヤーは、UIやデータがどのように保存されているかを気にしないので、UIやデータベースなどで構成されるこれらのインフラを簡単に変更できます。

用語の定義

上図の2つの円は、より詳細に定義することができます。

アプリケーション・ドメイン・レイヤはさらに、エンティティ、ユースケース、そしてドメイン・レイヤとインフラストラクチャ・レイヤを分離する中間適応レイヤに細分化されます。これらの用語を理解するのは少し難しいので、それぞれ個別に見ていきましょう。

エンティティ

エンティティ: アプリケーションの機能に不可欠な、関連する論理ルールの集合。オブジェクト指向プログラミング言語では、エンティティのルールはクラス内のメソッドのセットです。これらのルールは、アプリケーションがなかったとしても存在します。例えば、ローンに10%の利息をつけるというのは、銀行が持っているルールであり、それが紙の上で計算されようが、コンピュータ上で計算されようが、そのルールは変わりません。以下は、エンティティ・クラスについて説明した本の例です:

このエンティティは、他のレイヤーについて何も知らず、それらに依存しません。つまり、外側のレイヤーの他のクラスやコンポーネントの名前を使用しません。

ユースケース

ユースケースは特定のアプリケーションの論理的なルールです。ユースケースはシステムを自動化する方法とアプリケーションの振る舞いを決定します。この本のユースケースの例を示します:

Gather Info for New Loan
Input: Name, Address, Birthdate, etc.
Output: Same info + credit score
Rules:
 1. Validate name
 2. Validate address, etc.
 3. Get credit score
 4. If credit score < 500 activate Denial
 5. Else create Customer (entity) and activate Loan Estimation

ユースケースはエンティティーと対話し、エンティティーに依存しますが、外側のレイヤーについては何も知らず、ウェブページかアプリかも気にせず、データがクラウドに存在するかローカルのSQLiteデータベースに存在するかも気にしません。

このレイヤーはインターフェースを定義し、アウターレイヤーが使用できる抽象クラスを持ちます。

アダプター

アダプターはインターフェース・アダプターとも呼ばれ、アプリケーション・ドメイン層とインフラストラクチャのトランスレーターです。例えば、GUIからデータを受け取り、それをユースケースやエンティティが理解しやすい形に処理します。そして、ユースケースやエンティティから出力を受け取り、GUI表示やデータベース保存に便利な形に処理します。

インフラストラクチャー

このレイヤーには、UI、データベース、フレームワーク、デバイスなど、すべてのI/Oコンポーネントが含まれます。最も頻繁に変更されるレイヤーです。このレイヤーは変更しやすいので、安定したドメインレイヤーからできるだけ離れている必要があります。これにより、コンポーネントの変更や置き換えが容易になります。

シンプル・アーキテクチャーの実現理論

以下に説明する理論の名称は紛らわしいため、上記の説明や解説では意図的に使用していません。しかし、上記のアーキテクチャ設計を実装するためには、これらの理論に従う必要があります。この小節で混乱するようであれば、そのまま最終節まで読み飛ばしてください。

最初の5つの理論は、覚えやすいようにしばしばSOLIDと略されます。これらはクラスレベルの理論ですが、コンポーネントレベルの理論に似ています。コンポーネントレベルの理論は、SOLID理論から発展したものです。

Single Responsibility Principle

これはSOLID Sです。SRPは、クラスが1つの責任だけを持つべきであることを示します。クラスには、1つのことを達成するために一緒に働く多くのメソッドがあるかもしれません。クラスは一つの理由によってのみ変更されるべきです。例えば、財務管理部門がある理由でクラスを変更したいと考え、人事部門が別の理由でクラスを別の方法で変更したいと考えた場合、クラスを変更する理由が2つあることになります。この時点で、クラスは2つの異なるクラスに分割されるべきです。

Open Closed Principle

SOLIDのO。Openは拡張に対してオープン、Closeは修正に対してクローズという意味です。つまり、クラスやコンポーネントに機能を追加することは可能ですが、すでに存在する機能を変更することはできません。どうすればいいのでしょうか?1つのクラスやコンポーネントに1つの機能しかないようにし、最も安定したクラスをインターフェースの後ろに隠して、不安定なクラスが変更されても影響を受けないようにします。

Liskov Subbstitution Principle

SOLIDのLは、SUBSTITUTIONを思い出してください。この理論は、下位のクラスやコンポーネントは、上位のクラスやコンポーネントに影響を与えることなく置き換えることができるということを意味しています。これは、抽象クラスやインターフェースを実装することで実現できます。例えば、JavaのArrayListとLinkedListは、どちらもListインターフェースを完成させているので、互いに置き換えることができます。この理論をアーキテクチャレベルに適用すれば、ドメインロジックに影響を与えることなく、MySQLをMongoDBに置き換えることができます。

Interface Segregation Principle

SOLIDのI.ISPとは、あるクラスを、それを使う他のクラスから分離するためにインターフェイスを使うということです。インターフェースは、依存クラスが必要とするメソッドだけを公開します。こうすることで、他のメソッドが変更されても、依存クラスには影響しません。

Dependency Inversion Principle

SOLIDのD.は、不安定なクラスやコンポーネントは、より安定したクラスやコンポーネントに依存すべきであることを意味します。安定したクラスが不安定なクラスに依存している場合、不安定なクラスが変更されるたびに安定したクラスに影響が及びます。ですから、依存関係の方向を逆にする必要があります。その方法は?抽象クラスを使うか、インターフェイスの下に安定クラスを隠します。

そのため、安定クラスだけでなく、不安定クラスの名前も以下のように使用します:

class StableClass { void myMethod(VolatileClass param) { param.doSomething(); } }

不安定なクラスで実装されたインターフェースを作ることは可能です:

class StableClass { interface StableClassInterface { void doSomething(); } void myMethod(StableClassInterface param) { param.doSomething(); } } class VolatileClass implements StableClass.StableClassInterface { @override public void doSomething() { } }

これは依存関係の方向を逆転させます。不安定なクラスは安定したクラスの名前を知っていますが、安定したクラスは不安定なクラスについて何も知りません。

抽象ファクトリーパターン使うのも、これを実現する方法のひとつです。

Reuse/Release Equivalence Principle

REPはコンポーネント階層の理論です。再利用とは、再利用されるクラスやモジュールの集合を意味します。リリースとは、バージョン番号を付けて公開することを意味します。この理論が意味するのは、公開するものは一つの単位として再利用可能であるべきだということです。無関係なクラスのランダムな集合であってはなりません。

Common Closure Principle

CCPとはコンポーネント・レベル理論のこと。つまり、コンポーネントとは、同じ理由で同時に変化するクラスの集合であるべきだということです。もし異なる理由や異なる頻度で変化するのであれば、コンポーネントは分割されるべきです。基本的には単一責任原則と同じことを言っています。

Common Reuse Principle

CRPとは、コンポーネント階層の理論です。つまり、必要のないクラスを持つコンポーネントに依存すべきではないということです。ユーザーが必要のないクラスに依存する必要がないように、これらのコンポーネントは分割されるべきです。これも基本的には「インターフェース分離の原則」に相当します。

この3つの理論は互いに相反するものです。分割しすぎたり、組み合わせが多すぎたりするとトラブルの原因になります。これらの理論は、現在の状況に応じてバランスを取るべきです。

Acyclic Dependency Principle

ADPは、プロジェクトに依存の輪がないことを意味します。たとえば、コンポーネント A はコンポーネント B に依存し、コンポーネント B はコンポーネント C に依存し、コンポーネント C はコンポーネント A に依存して、依存の輪ができます。

このような依存関係のループは、システムに変更を加えようとしたときに大きな問題を引き起こす可能性があります。このようなループを断ち切る1つの解決策は、コンポーネント間にインターフェースのレイヤーを追加する「依存関係逆転の原則」を使うことです。もし異なる個人やチームが異なるコンポーネントを担当するのであれば、これらのコンポーネントはそれぞれのバージョン番号で別々にリリースされるべきです。こうすることで、あるコンポーネントの変更がすぐに他のチームに影響することはなくなります。

Stable Dependency Principle

この理論は、依存関係の方向は安定性と一致すべきであることを意味します。つまり、不安定なコンポーネントはより安定したコンポーネントに依存すべきです。これにより、変更の影響を減らすことができます。あるコンポーネントが不安定になるように設計されているのは構いませんが、安定したコンポーネントをそれに依存させるべきではありません。

Stable Abstraction Principle

つまり、安定したコンポーネントであればあるほど、より抽象的であるべきです。抽象クラスは拡張しやすいので、安定したコンポーネントが硬くなりすぎません。

最終章

以上、『クリーン・アーキテクチャー』の主な理論を要約しましたが、その他にもいくつか重要な点を付け加えておきます。

Testing

プラグイン・アーキテクチャを作成することで、コードをよりテストしやすくすることができます。依存関係が多すぎると、コードをテストするのが難しくなります。しかし、プラグインアーキテクチャを使えば、データベースの依存関係をモックオブジェクトに置き換えるのがずっと簡単になります。

GUIフローをテストし始めると、UIに変更を加えた途端にテストが止まってしまいます。その結果、テストを削除しなければならなくなりました。アダプテーションレイヤーにPresenterオブジェクトを作成する必要があることは理解しています。このPresenterオブジェクトは、ロジックルールの出力をUIが必要とする形式に整形します。UIオブジェクトはPresenterでフォーマットされたデータを表示するだけです。これにより、UI とは別に Presenter コードをテストすることができます。

ロジックルールをテストするために、専用のテスト API を作成します。アプリケーションの構造が変更されてもテストがクラッシュしないように、 インターフェイスアダプタから分離しましょう。

Dividing components by use cases

上記では、ドメインレイヤーとインフラレイヤーについて説明しました。これらが水平方向にレイヤー化されていると仮定すると、アプリのさまざまなユースケースに基づいてコンポーネントの組み合わせに垂直方向にスライスすることもできます。層のケーキのように、各スライスはユースケースであり、スライス内の各レイヤーはコンポーネントとして機能します。

例えば、動画サイトでは、ユーザが動画を視聴することがユースケースとなります。そのため、ViewerUseCase コンポーネント、ViewerPresenter コンポーネント、ViewerView コンポーネントなどがあります。もう 1 つのユースケースは、ユーザーが動画をアップロードすることです。その場合、PublisherUserCaseコンポーネント、PublisherPresenterコンポーネント、PublisherViewコンポーネントなどがあります。もう1つのユースケースは、ウェブサイトの管理者かもしれません。このように、別々のコンポーネントが垂直方向にスライスされた水平階層で作成されます。

アプリケーションがデプロイされるとき、これらのコンポーネントは理にかなった方法で組み合わせることができます。

まとめ

書籍『クリーン・アーキテクチャ』のエッセンスは、プラグイン・アーキテクチャを作ることです。同じ理由で同時に変更される可能性のあるクラスは、コンポーネントにまとめるべきです。アプリケーション・ロジックのコンポーネントはより安定しており、不安定なインフラストラクチャ・コンポーネントについては何も知らないはずです。コンポーネントの階層間の境界は、階層間でデータを変換するインターフェース・アダプターによって維持され、依存関係はより安定した内部コンポーネントの方向に向けられるべきです。

Further study

クリーンコード /アジャイルソフトウェア開発 /クリーンアーキテクチャ

Read next

msyqlのトランザクション分離についてどのくらい知っている?

トランザクションと言えばACIDを思い浮かべると思いますが、今日はその1つである "分離 "についてお話します。データベース上で複数のトランザクションが同時に実行されると、ダーティ・リードが発生したり、...

Jun 10, 2020 · 5 min read