ヘキサゴナルアーキテクチャとNetflix社での採用事例
ヘキサゴナルアーキテクチャについて理解を深めるために調べたので内容をメモする。
ヘキサゴナルアーキテクチャとは
概要
キサゴナルアーキテクチャの提唱者であるAlistair Cockburn氏の記事(2005年のものと思われる)が和訳されていたので、このセクションでは主にこの和訳記事を要約している。
ヘキサゴナルアーキテクチャ(Hexagonal architecture翻訳) | blog.tai2.net
ビジネスロジックがユーザーインターフェイスコードに侵入するのを防ぐためにPorts and Adaptersパターンを採用するアーキテクチャ。MVCやレイヤードアーキテクチャが持つ課題を解決するために生まれ、クリーンアーキテクチャの基礎となった。
DBや外部サービスなどの外部のものからアプリケーションのコアロジックを分離することで、テスト、保守、拡張が容易になるというアイデア。レイヤを増やすだけだとビジネスロジックがレイヤを超えて存在しないという取り決めをしても違反がないことを保証できない。アプリケーションをViewなどの外部と切り離し、提供する機能すべてをAPIにすることで、自動化された回帰テストを用いてビジネスロジックがプレゼンテーションレイヤーから隔離されていることを確認できる。そのAPIを外部デバイスが必要とするインターフェースに変えるのがadapterで、adapterとやりとりしてイベントを内部的に処理するための窓口(インターフェース)がport。
外部とはViewやコマンドなどユーザーが使用するもの、もしくはデータストレージを指すことが多く、入力と出力という一見反対側とも思えるそれらを同じadapter層で扱うのは、ユーザーサイドであれサーバーサイドであれ課題の本質は同じだから。その課題とは、ビジネスロジックと外部エンティティーとのやりとりが絡み合っているという、設計とプログラミングにおける誤り。
要はアプリケーションの内側にあるコアロジックを、外側に漏れないようにしたいというのが最大の関心ごと。
アーキテクチャを構成する概念
ビジネス ロジックを定義する主要な概念は、エンティティ、リポジトリ、およびインタラクターの3つ。コアが作用するものは全てアクターと呼ばれる。
- エンティティ:ドメイン オブジェクト。データがどこに保存されているかとは関係がない。
- リポジトリ:エンティティを取得したり、エンティティを作成および変更したりするためのインターフェイス。データソースと通信し、単数または複数のエンティティを返すために使用されるメソッドを保持する(例:UserRepository)。
- インタラクター:ドメインアクションを調整して実行するクラス。ビジネス ルールと検証ロジックの実装であり、サービス オブジェクトやユース ケース オブジェクトなどが含まれる。
ビジネスロジックの外側には、データソースとトランスポート層がある。両方Adapterだが、データソースはアプリケーションが利用するデータストレージへのアダプター(セカンダリアダプター)で、リポジトリ上で定義されたメソッドを実装し、データのフェッチとプッシュの実装を持つ。トランスポート層はユーザー向け(プライマリアダプター)なので、インタラクターを用いてビジネスロジックを実行する。 プライマリとセカンダリというのは、サービスを駆動する側か呼び出される側かということらしい。
レイヤの依存関係は全て内側を指す。六角形はポートの数を視覚的に表すという意味もあるようだが、分け方に明確な基準はなく、割と直感的で2~4くらいの少なさがいいんじゃないという感じらしい。
ちなみにNetflixでは、インタラクター、データソース、統合仕様(e2e)をテストしたそう。統合仕様によるテストは、ドメインアクションごとに1つずつ成功、失敗シナリオを行った。リポジトリはインターフェースなのでテストなし、エンティティはオブジェクトなのでメソッドがない場合はテストはしない。以上の内容について、単一プロセスで100秒で3,000の仕様のテストを実行した。依存しているサービスについては、契約テストを行うよう改善の余地があったとのこと。
Netflix社のヘキサゴナルアーキテクチャ採用事例
このセクションは、2020年3月のNetflixのテックブログ記事を著者の解釈に基づいてまとめており、翻訳ではない。
当時同社は、ビジネスの複数のドメインにまたがる新しいアプリの開発を行う際に、gRPC、JSON API、GraphQL などのさまざまなプロトコルを実装する多くのサービスに必要なデータが分散されており、それらを統合して使用する必要があった。 モノリスで開発を開始したが、時間の経過とともにアプリケーションが専門的になってくると、(パフォーマンスの問題ではなく)ドメインを分解するためにサービスを分解することにした。モノリスのアプリケーションのデータソースを、あるタイミングでマイクロサービスに切り替えるため、モノリスのアプリケーションをヘキサゴナルアーキテクチャで開発した、という状況のよう。
ヘキサゴナルアーキテクチャを採用してマイクロサービス化に備えることで大きなメリットがあった。
データは既に切り替え元と切り替え先両方のDBで同期されている状態にしておき、データソースの切り替えは1行のソースコードを変更するだけでよかった。結果として、データソースの切り替えは2時間以内で終わったそう。
また、外部との契約を先に決め、詳細な実装をカプセル化できることで、契約に基づいて外部サービスの開発の要求を損なうことなく内部実装を後から行うことができた。結果として、データをアプリケーション内に保存するかどうか、どのタイプのデータストアを使用するかなど、アーキテクチャを含む重要な意思決定を遅延させることができたとのこと。
まとめると、ヘキサゴナルアーキテクチャには下記のメリットがあった。
- ビジネスロジックに影響を与えずにデータソースを交換できる
- プロトコルに依存せずにビジネスロジックの検証をテストできる
- 適切な抽象化によりソースコードを大きく変更することなくデータソースを切り替えることが可能。結果としてロールバックが容易など、リリースのリスクが軽減される
- 内部実装のカプセル化と内部実装に関わる意思決定の遅延化。外部との契約としてのインターフェースを先に決定し、内部実装を後から具体化させることができる
最後の点については、プロジェクトのパラドックスを引き起こす、情報に基づかない意思決定により「アーキテクチャに自分たちを閉じ込めるべきではない」としている。プロジェクトのパラドックスとは、最大の意思決定を情報が最も少ないプロジェクト初期に行うことだそう(下記ツイート参考)。
The project paradox: making the biggest decisions when knowledge is at it's absolute lowest. pic.twitter.com/b7zBa4Aq7m
— Tobias Fors (@tofo) 2014年9月18日
ディレクトリ構成
プロジェクト構成はいくつかの記事で見かけたが下記のような感じが最も納得感がある気がした。
. ├── bin ├── cmd │ └── http ├── docs └── internal ├── adapter │ ├── cache │ │ └── redis │ ├── handler │ │ └── http │ ├── repository │ │ └── postgres │ │ └── migrations │ └── token │ └── paseto └── core ├── domain ├── port ├── service └── util
あとがき:クリーンアーキテクチャでいいのでは?
以上ヘキサゴナルアーキテクチャについて調べてみたが、私はクリーンアーキテクチャのレイヤを少し減らした感じで実装することが多かったので、多分時代的な背景が原因で、別にヘキサゴナルじゃなくてもクリーンアーキテクチャの亜種でいいじゃんという気持ちはやっぱり残った。大規模なモジュラモノリスとかを開発しているとよりメリットがわかるのかも?あまり想像できないが。MVCよりは全然境界分けやすそうというのは思ったし、プラガブルなアプリを使ってドメインを守るという強いコンセプトがあることは理解できた。デベロッパーエバンジェリストの成瀬氏によると、クリーンアーキテクチャはヘキサゴナルアーキテクチャの外側の具体的な実装を推し進めているとのことで、もしかしたらそもそもどちらがどう良いかという議論はそもそも不毛で、クリーンアーキテクチャほど詳細に外側の責務を分けたいわけじゃないなら、ヘキサゴナルくらいが内側と外側を明確に分離できて適任ということなのかもしれない。Netflixのテックブログ記事が2020年とクリーンアーキテクチャがそれなりに浸透した後のものであることを考えると、Netflixがなぜヘキサゴナルアーキテクチャを選択したのかは気になる。
また、DDDの文脈で語られることが多いが、別にドメインモデリングの話も一切出てこなかったし、ドメインロジックを守るという思想以外にヘキサゴナルが特段DDDに向いてそうな要素を持っているようには思えなかった。CodeZineの2018年の記事を読んだ感じ、時代的背景で当時最も合っていたと思われていたということなのかもしれない。開発者に対して「ドメインにフォーカスしよう」という明確なメッセージができることにより、たくさんレイヤを分けるアーキテクチャを採用することと比較し、外側とコアドメインというシンプルで強力なメンタルモデルをチームで共通化できることでコードを綺麗に保ちやすいという効果はあるかもしれない。