😇

アーキテクチャについて整理してみる

2024/11/26に公開

新人ソフトウェアエンジニアです。
アーキテクチャに関して教わってきましたが、頭の整理のためこの記事を書きます

レイヤードアーキテクチャ

基本的に以下の三層で構成されていて、各レイヤーは下のレイヤーに依存する

  • プレゼンテーションレイヤー(UI)
  • ドメインレイヤー(ビジネスロジック)
  • インフラストラクチャーレイヤー(データベースや外部APIとの通信)

この構成ではビジネスロジックを担うドメインレイヤーがインフラストラクチャーレイヤーに依存することになるためインフラストラクチャーレイヤーの変更がビジネスロジックにも影響する可能性があります。
例えば、データベースをMySQLからPostgreSQLに変更するとビジネスロジックの中のデータにアクセスするための関数をMySQLのものからPostgreSQLのものに変更する必要があります。
また、テストが依存先のコードの影響を受けてしまう可能性もあります。

ヘキサゴナルアーキテクチャ

レイヤードアーキテクチャではインフラレイヤーが依存の中心となっていましたが、本当はビジネスロジックを中心にしなければなりません
これを実現しているのがヘキサゴナルアーキテクチャです。この図でお馴染みのやつです。

レイヤードアーキテクチャではインフラストラクチャーレイヤーが依存の中心にあったのに対して、これはインフラストラクチャーレイヤーが外側にあり中心にはアプリケーションが配置されています。また、間にアダプターを挟むことでアプリケーションが直接インフラストラクチャーに依存することを避けています。ヘキサゴナルアーキテクチャのこの構成は依存性逆転の原則によって成り立っています。

依存性逆転の原則

依存性逆転の原則は依存の向きをコントロールするための考え方で以下がポイントになります。

  • 上位モジュールは下位のモジュールに依存するのではなく、どちらも抽象に依存するべき
  • 抽象は実装の詳細に依存せず、実装の詳細が抽象に依存するべき

特に抽象に依存させるという点が重要になります。例えば、usecaseがrepositoryに依存している状態を依存性逆転の原則に従って変更すると以下のようになる。
              usecase -> repository
ここにrepositoryのインターフェイスをかませる
          usecase -> IRepository <- repository

このようにするとusecaseとrepositoryは両方インターフェイス(抽象)に依存することになります。usecaseはインターフェイスを型として利用して具体的な実装には依存せず、repositoryはインターフェイスに合わせて実装を行うようになります。これによりusecaseとrepositoryの依存関係の逆転が実現しました。

依存性逆転の利点はモジュールの差し替えが可能になることです。上記の例だとusecaseのユニットテストを行う際にrepositoryをモックに差し替えることで依存先のコードにテストが依存することを避けられます。
         usecase -> IRrepository <- mock
このようにusecaseはIRepositoryに依存しているため、repositoryを変更してもusecaseに影響はありません。

ヘキサゴナルアーキテクチャではビジネスロジックが中心にあり、外側のインフラストラクチャーレイヤーとのやり取りはポートとアダプタを介して行われます。ここでいうポートとは抽象のことであり、アダプターはその抽象に依存した具体的な実装を表します。アダプターを変更することでインフラストラクチャーレイヤーの具体的な実装(データベースや外部APIとのやり取り)がビジネスロジックに影響を与えることなくシステムの変更や拡張を行うことができます。

オニオンアーキテクチャ

オニオンアーキテクチャもヘキサゴナルアーキテクチャと同じように依存性逆転の原則で依存の方向が外側のインフラストラクチャーレイヤーから内側のドメインレイヤーへ向くようになっています。さらにそれに加えてアプリケーションをアプリケーションサービス、ドメインサービス、ドメインモデルに分けることで関心ごとの分離をし、密結合を避けています。
結合度が低くなることで以下のメリットがあります。

  • 変更の影響範囲を限定できる
     結合度が低いとモジュール間の依存が少ないため、あるモジュールを変更してもそれが他のモジュールに影響を及ぼしにくくなります。
  • 再利用がしやすい
     結合度が低いとモジュールやコンポーネントがそれ単体で独立して機能するため他の部分で再利用がしやすくなります。
  • テストがしやすい
    モジュール間の依存が少ないため他のモジュールをモックに置き換えることができます。
  • エラーやバグが特定しやすい
    各モジュール間の影響が限定的なため、エラーやバグの原因箇所が絞りやすくなります。

ヘキサゴナルアーキテクチャがポートとアダプタで外部とのやり取りの抽象化に焦点を当てていたのに対してオニオンアーキテクチャは依存関係を明確に分離することに焦点を当てています。

クリーンアーキテクチャ

クリーンアーキテクチャとはアーキテクチャパターンそのものというよりシステム設計をする際に守るルールです。
Clean Architectureの著者ロバート・マーティンによるとクリーンアーキテクチャに則り設計されたシステムには以下の特性があるようです。

  • フレームワーク非依存
    システムはライブラリやフレームワークに依存しない。これによりフレームワークの制約でシステムを縛るのではなくツールとしてフレームワークを利用することができる。
  • テスト可能
    ビジネスルールはUIやデータベースなどの外部要素がなくてもテスができる。
  • UI非依存
    UIはシステムの他の部分を変更することなく変更できる。
  • データベース非依存
    他のデータベースに置き換えることができる。
  • 外部エージェント非依存
    ビジネスルールは外部のインターフェイスについて何も知る必要がない。

要するにクリーンアーキテクチャは、ソフトウェアを異なるレイヤーに分割し、依存の向きを外から内にコントロールすることによって関心事の分離を目指しています。外側のレイヤーから内側のレイヤーへの依存のみを許可することで、ビジネスロジック(エンティティーとユースケースレイヤー)が技術的な詳細から独立して、保守性とテストのしやすさが向上します。

まとめ

各アーキテクチャの整理のためにこの記事を書きましたが、結局はそれぞれの責務を明確にした上で、依存の向きをビジネスロジックに向けることでビジネスロジックが他のレイヤーの影響を受けないようにすることが重要だなと思いました。これを常に意識していればアーキテクチャパターンに厳密に従わなくてもプロジェウトの要件に合わせて柔軟に対応していけるのではと思いました。
もちろんまだ柔軟な対応ができない自分にとっては具体的なアーキテクチャパターンはとても有用ですが、くれぐれもアーキテクチャが目的にならないようにこの本質的なルールを頭に叩き込んで日々開発していこうと思います。

Discussion