ここ最近Vaughn Vernonさんの『実践ドメイン駆動設計』(以下iDDD)を読んでいました。この本では数々のアーキテクチャを紹介してくれているのですが、そのなかでも特に「ヘキサゴナルアーキテクチャ」が取り上げられることが多いのですが、難解でなかなか理解できないでいました。
読み終わってから、いろいろ調べて見た結果、いくつか分かったことがあったため、簡単に紹介したいと思います。
ヘキサゴナルアーキテクチャとは
ヘキサゴナルアーキテクチャはAlistair Cockburnが提唱したアーキテクチャで、Ports and Adaptersとも呼ばれています。原文、及び日本語訳はこちら。
ヘキサゴナルアーキテクチャはシステムを外部と内部の2つの領域に分けてテストのしやすさ、外的環境の変化への適応のしやすさに重きをおいた設計です。内部というのはシステムのビジネスロジックそのもので、外部というのは外部からのリクエスト、例えばhttpやCLIコマンド、メッセージキューと、システムが使う外部の別のシステム、例えばMySQLやメールサーバなどが相当します。
ヘキサゴナルアーキテクチャではビジネスロジックは完全に外部から切り離されているため、それ単体でテストを実行することができます。また、MySQLを使ったテストなどもモックオブジェクトを使うことで簡単にテストを行うことができます。このようにヘキサゴナルアーキテクチャは外部環境を抽象化することが前提になっているため、TDDとも非常に相性が良さそうです。またMySQLからNoSQLに切り替えたりなど、外部環境が変わったとしても外部環境と内部のシステムを仲立ちする"Adapter"を変更するだけで良く、ビジネスロジックを変更する必要がないため、安定性の高いアーキテクチャとなっています。
DDDにおけるヘキサゴナルアーキテクチャとオリジナルのヘキサゴナルアーキテクチャ
iDDDでのヘキサゴナルアーキテクチャは理解するのが大変だったため、一度Cockburnさんの元記事を見ることにしました。
この記事では、入力した元値によって割引率と割引後の価格が計算されるアプリケーションが例として上げられています。またGithubにはそのアプリケーションをRuby+Rackで書き直したものが紹介されており、Rubyやウェブアプリケーションに馴染みのある方にはそちらのほうが理解しやすい内容となっています。
Ruby + Rackでのヘキサゴナルアーキテクチャ
hex = SmallerWebHexagon.new(InCodeRater.new)
app = RackHttpAdapter.new(hex,"./src/views/")
run app
config.ruがアプリケーションの入り口になっています。
InCodeRater
は割引率を決定するビジネスロジックが埋め込まれたオブジェクトです。このアプリケーションではここを別の割引率算定ロジックを含んだオブジェクトに変更ことも可能です(ただしrate
メソッドを実装していることが前提です)。この例ではドメイン駆動設計は考慮されていないので特にドメインの概念は登場しませんが、InCodeRater
はいわゆるビジネスロジックそのもので、iDDDでいうところのドメイン層に属するものです。少し形を変えていけば、DomainService
やEntity
、ValueObject
にもなりうる内容です。
class InCodeRater
def rate value
case
when value <= 100
1.01
when value > 100
1.5
end
end
end
SmallerWebHexagon
は引数によって渡された割引率算定オブジェクトを元に割引率と、割引後の価格を計算します。iDDDでいうところのアプリケーション層にあたり、内部でドメインオブジェクトを作成し、アプリケーションのためのタスクの調整をしています。
class SmallerWebHexagon
def initialize rater
@rater = rater
end
def rate_and_result value
rate = @rater.rate(value)
result = value * rate
return rate, result
end
end
最後にRackHttpAdapter
オブジェクトが生成され、Rackによってrun
されます。
class RackHttpAdapter
def initialize(hex_app, views_folder)
@app = hex_app
@views_folder = views_folder
end
def call(env) # hooks into the Rack Request chain
request = Rack::Request.new(env)
value = path_as_number(request)
rate, result = @app.rate_and_result value
out = {
out_action: 'result_view',
value: value,
rate: rate,
result: result
}
template_path = Pathname.new(@views_folder).join(out[:out_action]).sub_ext('.erb')
page = html_from_template_file(template_path , binding)
response = Rack::Response.new
response.write(page)
response.finish
end
private
#以下省略
end
このとおり、Adapterはブラウザからのリクエストを受け取り、パラメータを取得します。
そしてアプリケーションが安全に動くようにパラメータを適切な形に変換します。
このプログラムでは省略されていますが、パラメータなどの入力データのバリデーションもこの層で行われると思います。
そしてrate, result = @app.rate_and_result value
でアプリケーションそのものを実行しています。
最後に、iDDDでというところのプレゼンテーション層の責務もここで実行されています。
この例を読むとヘキサゴナルアーキテクチャそのものは非常にシンプルな構造になっていることが分かります。
ただ、iDDDではヘキサゴナルアーキテクチャのアプリケーション層を、アプリケーション層とドメイン層の2つに分割しているため、構造が複雑になり、理解が難しくなっていると感じました。
まとめ
CockburnさんのサンプルとiDDDでのヘキサゴナルアーキテクチャのレイヤーの対応表を作ってみました。
Cockburnさんの例 | iDDD |
---|---|
RackHttpAdapter | Adapter |
SmallerWebHexagon | Application |
InCodeRater | Domain |
結論
- iDDD(やその他の図書)を読んでヘキサゴナルアーキテクチャにハマったらCockburnさんの記事を読むといいと思います。
- rubyやウェブアプリケーションに慣れている人であれば、こちらのサンプルを見ると理解が捗ると思います。
- ヘキサゴナルアーキテクチャは意外とシンプルです。
今流行りのクリーンアーキテクチャもヘキサゴナルアーキテクチャを参考にしていることもあり、設計思想はとても似ています。今後のためにもぜひヘキサゴナルアーキテクチャを理解してみてください!