Rubyのサンプルソースで見るヘキサゴナルアーキテクチャとDDD

ここ最近Vaughn Vernonさんの[『実践ドメイン駆動設計』](https://www.amazon.co.jp/実践ドメイン駆動設計-Object-Oriented-SELECTION-ヴァーン・ヴァーノン/dp/479813161X/ref=sr_1_1?ie=UTF8&qid=1534142588&sr=8-1&keywords=実践DDD)(以下iDDD)を読んでいました。この本では数々のアーキテクチャを紹介してくれているのですが、そのなかでも特に「ヘキサゴナルアーキテクチャ」が取り上げられることが多いのですが、難解でなかなか理解できないでいました。

読み終わってから、いろいろ調べて見た結果、いくつか分かったことがあったため、簡単に紹介したいと思います。

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

ヘキサゴナルアーキテクチャはAlistair Cockburnが提唱したアーキテクチャで、Ports and Adaptersとも呼ばれています。[原文](http://alistair.cockburn.us/Hexagonal+architecture)、及び[日本語訳](https://blog.tai2.net/hexagonal_architexture.html)はこちら。

ヘキサゴナルアーキテクチャはシステムを外部と内部の2つの領域に分けてテストのしやすさ、外的環境の変化への適応のしやすさに重きをおいた設計です。内部というのはシステムのビジネスロジックそのもので、外部というのは外部からのリクエスト、例えばhttpやCLIコマンド、メッセージキューと、システムが使う外部の別のシステム、例えばMySQLやメールサーバなどが相当します。

ヘキサゴナルアーキテクチャではビジネスロジックは完全に外部から切り離されているため、それ単体でテストを実行することができます。また、MySQLを使ったテストなどもモックオブジェクトを使うことで簡単にテストを行うことができます。このようにヘキサゴナルアーキテクチャは外部環境を抽象化することが前提になっているため、TDDとも非常に相性が良さそうです。またMySQLからNoSQLに切り替えたりなど、外部環境が変わったとしても外部環境と内部のシステムを仲立ちする"Adapter"を変更するだけで良く、ビジネスロジックを変更する必要がないため、安定性の高いアーキテクチャとなっています。

## DDDにおけるヘキサゴナルアーキテクチャとオリジナルのヘキサゴナルアーキテクチャ

iDDDでのヘキサゴナルアーキテクチャは理解するのが大変だったため、一度Cockburnさんの[元記事](https://blog.tai2.net/hexagonal_architexture.html)を見ることにしました。

この記事では、入力した元値によって割引率と割引後の価格が計算されるアプリケーションが例として上げられています。また[GithubにはそのアプリケーションをRuby+Rackで書き直したもの](https://github.com/totheralistair/SmallerWebHexagon)が紹介されており、Rubyやウェブアプリケーションに馴染みのある方にはそちらのほうが理解しやすい内容となっています。

### Ruby + Rackでのヘキサゴナルアーキテクチャ

```ruby:config.ru
hex = SmallerWebHexagon.new(InCodeRater.new)
app = RackHttpAdapter.new(hex,"./src/views/")
run app
```
config.ruがアプリケーションの入り口になっています。
`InCodeRater`は割引率を決定するビジネスロジックが埋め込まれたオブジェクトです。このアプリケーションではここを別の割引率算定ロジックを含んだオブジェクトに変更ことも可能です(ただし`rate`メソッドを実装していることが前提です)。この例ではドメイン駆動設計は考慮されていないので特にドメインの概念は登場しませんが、`InCodeRater`はいわゆるビジネスロジックそのもので、iDDDでいうところのドメイン層に属するものです。少し形を変えていけば、`DomainService`や`Entity`、`ValueObject`にもなりうる内容です。

```ruby
class InCodeRater
  def rate value
    case
      when value <= 100
        1.01
      when value > 100
        1.5
    end
  end
end
```

`SmallerWebHexagon`は引数によって渡された割引率算定オブジェクトを元に割引率と、割引後の価格を計算します。iDDDでいうところのアプリケーション層にあたり、内部でドメインオブジェクトを作成し、アプリケーションのためのタスクの調整をしています。

```ruby
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`されます。

```ruby
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さんの記事](https://blog.tai2.net/hexagonal_architexture.html)を読むといいと思います。
- rubyやウェブアプリケーションに慣れている人であれば、[こちらのサンプル](https://github.com/totheralistair/SmallerWebHexagon)を見ると理解が捗ると思います。
- ヘキサゴナルアーキテクチャは意外とシンプルです。

今流行りのクリーンアーキテクチャもヘキサゴナルアーキテクチャを参考にしていることもあり、設計思想はとても似ています。今後のためにもぜひヘキサゴナルアーキテクチャを理解してみてください！