新人育成用に自分が知っている知識を棚卸するための記事です。なお、解釈が誤っている可能性は十分にあります。
キーワード
- ポートアンドアダプター
- ヘキサゴナルアーキテクチャ
- ミュータブル/イミュータブル
- 腐敗防止層
ポートアンドアダプター(ヘキサゴナルアーキテクチャ)とは
私がアーキテクチャで意識しているのは、次の点です。
- アプリケーションの「内側」と「外側」の区別をつける
- 「外側」の知識を「内側」に侵入させない
- 「内側」の知識を「外側」に流出させない
アプリケーションの「内側」と「外側」の区別をつける
私は「内側」「外側」を次の定義で表現しています。
- 内側
- アプリケーションのビジネスロジック
- 外側
- アプリケーションで完結できない、実行時例外が発生しやすいロジック
- HTTPを受信するController、DBと接続するRepository、他システムとRESTやSOAPで連携、ファイル操作
この「外側全体」を「ポート」と呼び、ControllerやRepository等の「外側の機能1つ1つ」を「アダプター」と呼びます。
ミュータブルとイミュータブル
ミュータブルはインスタンス生成後に設定変更が可能なこと、イミュータブルは設定変更が不可能なことを指します。基本的にはミュータブルだと、NullPointerExceptionを発生しないように操作順序を意識する必要があったり、思わぬバグを仕込んでしまう可能性があるので、可能な限りイミュータブルにすることが求められます。私が踏んだバグの例だと、ログにクレジットカード番号を出力しないように「***」とマスクしていたのですが、元のインスタンスが書き換えられていたので後続の処理が通らなくなっていました。
ポートアンドアダプターを意識しているコードで単体テストを書いていると「外側」の方が「内側」と比べて、HTTP通信やDBの準備が必要で大変なことが多いです。また、「外側」の機能は自分で開発せずに、Request/Responseのマッピング等をライブラリの機能によって担保していることも多いです。しかし、Request/Responseのマッピングする際には、ライブラリの都合でgetter/setterが定義されていたり、空のコンストラクタが定義されていることを求められることが多々あります。
そうすると、マッピングした項目はミュータブルにせざるを得ません。もちろん、自分で開発すればイミュータブルなインスタンスを作成することはできますが、開発工数には合わないと判断することが多かったです。
ここで伝えたいことは「内側」と比べて「外側」はミュータブルにせざるを得ない状況が多くあるということです。
「外側」の知識を「内側」に侵入させない
「外側」の知識を「内側」に侵入させないことは重要です。
その「外側」の知識を「内側」に侵入させないようにする仕組みとして腐敗防止層があります。腐敗防止層についてはこちらの記事を参照してください。
余計なライブラリ情報を流出させないことも重要です。例えば、JavaのDBアクセスするライブラリにMyBatis
があります。このMyBatis
にはSQLのOFFSET
とLIMIT
を纏めたorg.apache.ibatis.session.RowBounds
クラスが存在します。ページングが必要なSQLにて重宝しますが、このクラスはControllerで意識する必要はあるでしょうか。おそらく、ありません。
もちろん、判断によってはControllerでRowBounds
を使用しても問題ありません。しかし、Javaのマルチプロジェクトで運用していると、Controllerプロジェクトの依存関係に明示的にMyBatis
を読み込ませる必要があります。もし、今後マルチプロジェクトにしたい等の要望があった時に備えて、各アダプターごとに依存を完結しておくとよいでしょう。
「外側」の知識を「内側」でそのまま使用すると大変なことになります。その例としてミノ駆動様のUserクラス動画があります。動画や資料の通り、「User」をそのまま使用するのは危険です。必ずアプリケーションで必要なモデリングを行い、「内側」では「外側のUserクラス」から翻訳した「内側のUserクラス」を使用するようにしましょう。
twitter.comクソコード動画「Userクラス」 #DXD2021 pic.twitter.com/JOIIl8IuwS
— ミノ駆動 (@MinoDriven) 2021年4月10日
「内側」の知識を「外側」に流出させない
例えば、メールを送るシステムにデータ連携をする場合、メールの元となるデータ作成ロジックはどこに入れるべきでしょうか。
- 内側
- アダプターのメールシステム
こちらは、「内側」で作成するべきです。どんなメール文面にするか、誰に送るかといった情報は「外側」に流出させるべきではありません。「内側」でデータを作成し、「外側」は「内側」から受け取ったデータをメールシステムに連携できるように加工すると良いでしょう。
例えば、特定のクエリパラメータが存在しないとき、デフォルト値で検索するシステムがあったとします。その場合、デフォルト値で補完するロジックは次のどこに入れるべきでしょうか。
- アダプターのController
- 内側
- アダプターのRepository
こちらは判断が分かれます。「内側」と言いたいところですが、API仕様書にデフォルト値で補完する旨を書いているのであれば、そのロジックは「外側」の持ち物なのでアダプターのControllerで行っても良いと考えています。
終わりに
松岡様のQiita記事によると、ポートアンドアダプター(ヘキサゴナルアーキテクチャ)とオニオンアーキテクチャ、クリーンアーキテクチャは本質的に同じものだそうです。個人的にはオニオンアーキテクチャに寄せていきたいのですが、DDDをチーム全員に理解してもらう必要があるので、導入には二の足を踏んでいます。特に私がドメインサービスとアプリケーションサービスの違いを明確に理解していないので不安です。
アーキテクチャに正解はありません。しかし、同一の理解をしておくことで、どこにロジックを入れるべきかを話し合うことができます。心理的安全性を確保して知的コンバットでより良くしていきたいですね。
この記事お役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。
参考
- [DDD]ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か - Qiita
- ヘキサゴナルアーキテクチャ(Hexagonal architecture翻訳) | blog.tai2.net
- Hexagonal architecture (software) - Wikipedia
- 画像取得元
- 【野中郁次郎氏対談】第2章 徹底的な対話による「知的コンバット」なくして、イノベーションは生まれない | Hello, Coaching!