概要
HexagonalArchitecture(ヘキサゴナルアーキテクチャ)についての解説です。
ヘキサゴナルアーキテクチャはDomain Driven Design(DDD, ドメイン駆動設計)に連なる設計手法です。
レイヤードアーキテクチャを更に疎結合に推し進めた形です。
別名として、Ports and Adapters(ポートアダプター)とも呼ばれています。
サンプル
何はともあれサンプルコードです。
https://github.com/nrslib/HexagonalArchitectureSample
解説
ヘキサゴナルアーキテクチャは以下のような図が有名です。
この図の形が六角形のため、ヘキサゴナルと表現されました。
なお、六角形であることは重要な要素ではありません。
こちらの図はドメイン領域を中心に見据え、データの入力や反映処理を外側に押しやるように設計するということを図に表しています。
図に表れているキーワードは Adapter, Port, Application の三種類です。
特に Web や App に近い Adapter は要するにユーザーのアクターであり、Primary adapter と呼ばれています。その領域の Port は Primary port と呼ばれます。
また、逆側の Adapter は Application がデータの問い合わせや反映の処理を行う Application のアクターになっており、Secondary adapter と呼ばれます。Primary と同じように Port も Secondary port と呼ばれています。
さらにより一層イメージしやすいように具体的なものに置き換えた図が以下です。
何がどこに位置するのかを明示することで、イメージが付くようになった方もいるのではないでしょうか。置き換わったものの詳細な説明は後述するとして、今の段階では表面的な解説を続けます。
ドメイン領域を中心に見据えてそのほかを外側に押しやる設計をするということですが、その動機はどこにあるでしょうか。
ヘキサゴナルアーキテクチャがそのように設計する動機として挙げられるのは、UI や DB(特に UI )はドメイン領域に比べ、変化をしやすい箇所であることです。
変化しやすい UI などで発生した変化に引っ張られて、ドメイン領域まで修正しなくてもよいようにするという設計がヘキサゴナルアーキテクチャの目的です。
具体的にドメイン領域を保護する方法として、ヘキサゴナルアーキテクチャは図における内側のレイヤーが外側のレイヤーに依存しないようにします。
依存しない、という意味がしっくりこない方は、依存しない=コンストラクタで interface や抽象クラスを受け取るようにするという意味で考えるとわかりやすいでしょう。
逆に interface や抽象クラスを受け取らず、内部で具象クラスをインスタンス化などをすると、それは具象クラスに対して依存していることになります。
つまり依存とは具体的なクラスに”依存”しているという意味です。
このアーキテクチャのメリットは、修正の影響範囲が外側に局所化されることに加え、結果として各コンポーネントは疎結合になるため、テストが行いやすくなることが挙げられます。
また、明快にレイヤー分けがされているため、何をどこに書くのかという基準が明快になります。何をどこに書くのかが明快ということは、処理を確認したい時などに該当のソースコードを探す手間が省け、リーディングの時間も減ります。
デメリットはコード量が増えることと、根底にある DDD への理解やコード上の矛盾を解決するための手法( DI 等)の理解が必要となり、全くの初心者からすると学習コストが高くなります。
用語説明
処理の流れに沿って用語の説明をして行きます。
自身をユーザと考えた場合、この図と実際のフローを照らし合わせると左上から始まり右下へ進むので、左上から順に解説していきます。
Primary Adapter
プライマリーアダプターはユーザの入力などドメイン領域に対して外界からの接触を図るためのモジュールです。
このモジュールは HttpRequest や UI の入力などの様々な入力データを加工して、プライマリーポートへ適合させます。適合する = Adapter です。
システムを利用できるデバイスやサービスが増えるに従い、必要に応じて増やすことができます。
具体的な例としては
- MVC フレームワーク
- テストモジュール
- コマンドライン
などがプライマリーアダプターにあたるでしょう。
MVCフレームワークがここに配置されることに驚く方もいるかもしれません。
ヘキサゴナルアーキテクチャの原文記事(http://alistair.cockburn.us/Hexagonal+architecture)においても
The MVC pattern was implemented as early as 1974 in the Smalltalk project. It has been given, over the years, many variations, such as Model-Interactor and Model-View-Presenter. Each of these implements the idea of ports-and-adapters on the primary ports, not the secondary ports.
といったように MVC パターン自体はプライマリーポート上のポートとアダプターの概念の実装であるとされています。
これは View から受け取った入力を、Controller(プライマリーアダプター) で Model が必要とする入力(プライマリーポート)に加工–適合させて実行しているということを指していると思われます。
よって、MVC フレームワークはプライマリーアダプターとプライマリーポート双方に跨る、と定義してしまいたいところですが、プライマリーポートが MVC フレームワークに包括されると、他のアダプターを作ることができなくなる(例えば GUI アプリを作った時)ため、MVC フレームワークはプライマリーアダプターとして扱います。
Primary Port
プライマリーポート。処理のインターフェースです。
API でイメージすると想像しやすいでしょう。
殆どユースケースと同じになるかもしれません。
ある一つのプロトコルに集約されるかもしれません。
重要なのは、どういった形でどれだけのポートが作られるかではなく、様々な外界からの入力を一つの操作に集約することで、外界での変化の影響をドメイン領域に及ぼさないようにすることです。
Application
このレイヤーでドメイン領域を表現します。
何を問題にし何を解決するシステムであるかにより大きく異なりますが、DDD を実践するにあたっては AggregateRoot (AR,集約)や ApplicationService, DomainService などが登場人物として挙げられます。
ここで起きた仕様変更は他のレイヤーに波及することがありえます。
Secondary Port
永続化されたデータへの問い合わせや副作用を目的とした処理のインターフェースです。
問い合わせをする際には Application がこれを利用し AR を取得します。
AR を取得する手段として Repository の interface や抽象クラスがここを担います。
勿論、ただの Api のインターフェースであることもあります。
Secondary Adapter
AR の再構築や副作用を目的としたコマンドの実行部分です。
Oracle や MySQL 等の RDB へのデータ問い合わせや、Api の呼び出しなどはここに記述されます。
InMemory なテストを行う際には、データベースの動作を模した実装を行います。
コードを見ながら
ヘキサゴナルアーキテクチャは決まった形があるわけではありません。
冒頭でも説明した通り、まずドメイン領域=ビジネスロジックを中心に見据え、その操作をポートとして開放し、必要に応じていくつものアダプターを作ることができるということが本質です。
とはいえ、ある程度の形がないとイメージしづらいため、記事の最初の方でも提示しましたがサンプルを用意しました。
https://github.com/nrslib/HexagonalArchitectureSample
ヘキサゴナルアーキテクチャに従って、ブログに記事を投稿するという簡単なシステムです。
プロジェクト構成をまずは確認します。
- Application
- Domain
- InMemoryDataStore
- Presentation
- MyLibrary
どこにもポートやアダプターという言葉は出てきません。
ポートやアダプターというのは実際にはそういったレイヤーが存在するというだけであり、あえて Port などの名前を付けるよりも実に沿った名前を付けています。
特にアダプターに関しては一つのポートに対して複数存在することが多いため、SecondaryAdapter というプロジェクト名称にするとテスト用の InMemory なセカンダリアダプターと本番環境の DAO を活用したセカンダリアダプターが同一ディレクトリに存在することになります。
全く用途の異なるものを同居させるよりは、目的に即した適切な名前をつけておく方が賢い戦略でしょう。
切るものだからといって、包丁とノコギリを同じ棚に入れておかないのと同じことです。
それはさておき、各種プロジェクトの担当個所を割り振ると以下のようになります。
- Application -> Primary Port
- Domain -> Application, Secondary Port
- InMemoryDataStore -> Secondary Adapter
- Presentation -> Primary Adapter
- MyLibrary
Secondary Port については Domain プロジェクトと分割すると構成的にわかりづらくなるため、Domain プロジェクトが二つの層を保持しています。
Primary Adapter
まずはプライマリーアダプターから見て行きます。
プライマリーアダプターの主たる処理は Presentation/Controller/ArticleController.cs で実装されています。
データを加工しプライマリーポートである IArticleService のメソッドを Call しているのがわかると思います。
また本来行う必要がないことですが、ArticleController はプライマリーポートにも依存しないようにされています。これは Presentation layer のテスタビリティを確保するために行いました。詳しくは後述します。
このプロジェクトでは MVC の実装のみですが、 Application レイヤーのテストをする場合は、任意のデータを用いてプライマリポートのインターフェースを呼び出す実装を作ることになります。
また、将来的にモバイル対応や Api として利用することになるかもしれません。
その際にはそれぞれ専用のプライマリーアダプターを作成することになります。
Primary Port
プライマリーポートは Applicatiom/ApplicationService/Articles/IArticleService.cs です。
殆どユースケースと 1:1 になるようにメソッドを用意しています。
プライマリーポートのメソッドは引数として Data transfer object(DTO, データ転送オブジェクト) を受け取っています。
外界の些末な変化はこの DTO によって丸められ、ドメイン領域には届かないようになっています。逆に DTO が変化する = ドメイン領域で変更があったということになり、その変化は外界にも影響を及ぼすことになります。
ついで、戻り値についても DTO を今回は戻しています。
しかし、これは必ずしも DTO である必要はありません。
例えば Data transformer をプライマリポート層で定義しておけば、プライマリアダプターにおいてそれを実装したものをプライマリーポートに引き渡し、DTO 以外の任意のオブジェクトを戻すことが可能になります。他にもドメインイベントとして結果を戻すことが考えられます。
とはいえ、そういった些末なことについては本質的でないため、わかりやすさを求めて DTO を戻すように記述してあります。
Application
ドメイン層の実装です。
プライマリーポートの実装を確認します。
着目すべきは Repository(セカンダリーポート) を interface で受けることにより、セカンダリーポートに依存していないことです。
そのほかについては DDD のよくある実装ですので特筆すべき実装はありません。
それでも敢えて挙げるのであれば、C# にしては珍しく Option を活用しているのと、Data transformer を活用して、AR から getter を排除していることでしょうか。
Secondary Port
セカンダリーポートの Repositorty は AR と同じ namespace に配置しています。Repositoryは AR と密接な関係にあり、近い位置に配置しておいた方が見通しがよくなるからです。
こちらもプライマリーポートと同じく、AR の再構築方法についてのインターフェースが主となっています。
Secondary Adapter
セカンダリーアダプターについて、今回は連想配列を用いたテスト用のスタブを用意しました。
おそらく将来的には product 環境用に RDB のデータを読み取るセカンダリーアダプターが作られるでしょう。もしかしたらApiかもしれません。
このようにセカンダリーアダプターの実装が決まらないうちからプロジェクトを進めることができるというのも大きな利点の一つです。
その他の話題
Dependency Injection
Controller や ArticleService のコンストラクターで引き渡している interface は誰が渡しているでしょうか。
今回のプロジェクトにおいては Web プロジェクト特有の実装で、Dependency injection(DI, 依存性注入) を行っています。
DI フレームワークとして利用しているのは .NET Framework の ASP.net MVC プロジェクトですので Unity です。
RegisterTypes() メソッドにおいて、interface とそれに割り当てたい具象クラスを登録しています。
今回利用している ArticleRepository については連想配列をデータベースに見立てた実装なので、インスタンスが何度も作り直されてしまっては永続化できません。そのため Singleton になるように登録しています。
この登録を変更することにより
- Repository は自環境のデータベースに接続する
- 再現が難しいエラーを意味する DTO を返却する Service を利用して、画面の動作を確認する
といったことを、コードを変更せずに行うことが実現可能になります(サンプルでは UnityConfig.cs のコードを変更しますが、設定ファイルから読み込むようにすることも可能です)。
まとめ
以上がヘキサゴナルアーキテクチャの解説です。
再度お伝えしますが、ヘキサゴナルアーキテクチャは決まった形があるわけではありません。
ドメイン領域を中心に見据え、外部への依存を無くす目的を持った設計こそがヘキサゴナルアーキテクチャです。