goddd とは何か?

これは Go Advent Calendar 2017 その2 6日目の記事です。
https://qiita.com/advent-calendar/2017/go2

みなさん、こんにちは。
pospome です。
普段は GAE/Go でサーバサイドの開発をしています。

twitter では 実装パターン, DDD, golang, GCP についてつぶやくことが多いので、
同じような分野に興味があれば、
フォローしてマサカリ投げてもらえると嬉しいです。
https://twitter.com/pospome

ということで本題に入ります。

golang.tokyo #9 で goddd という github リポジトリを知りました。
https://golangtokyo.connpass.com/event/65921/

ちょっと興味があったので、
goddd に対する自分の感想を書いていこうと思います。

goddd はDDD本のサンプル実装を golang に移植したもの

リポジトリは以下。
https://github.com/marcusolsson/goddd

cargo というディレクトリがあるので、
DDD本のサンプル実装を golang で書き直したものということが分かる。

README.md にも書いてありますね。

This is an attempt to port the DDD Sample App to idiomatic Go. This project aims to:

Demonstrate how the tactical design patterns from Domain Driven Design may be implemented in Go.
Serve as an example of a modern production-ready enterprise application.

自分も過去に PHP, Ruby で実装を考えたことがあるので、
それの golang バージョンというところでしょうか。

レイヤ構造

特徴的なのはレイヤ構造が明示的に存在しないこと。
つまり、presenter, application, domain, infra というパッケージが存在しない。

プロジェクトルートにある cargo, booking などのパッケージが、
domain だったり、
application だったりする。

例えば、
booking は application に相当するもので、
cargo は domain に相当する。

パッケージ内には presenter 相当の実装が入ってたりするので、
機能ごとの縦割りパッケージ構成に近い印象を受ける。

routing は domain service の interface でありながら、
実装(HTTP通信)も同じパッケージに持っているので、
DIPにはなっていない。

通常のレイヤ構造だと domain に interface を置き、
infra に実装を置くのだが、
そもそも goddd は明示的なレイヤ構造になっていないので、
問題ない気もする。

思ったが、
プロジェクトルートには mongo, inmem という infra 相当の概念も置かれていて、
それらの interface は domain に相当するパッケージに配置されている。
つまり、ここは DIP になっているみたい。

プロジェクトルートに各レイヤのパッケージを直置きしているので、
この実装のレイヤ構造としては、
見えないレイヤ構造が存在する or そもそもレイヤ構造ではない
という感じだろうか・・・。

プロジェクトルートに各レイヤのパッケージを直置きしているのは
golang っぽいパッケージ構成だなーと感じた。

このパッケージ構成(レイヤ構造)について気になる点は
そのパッケージが domain なのか、
domain 以外なのか が判別しづらい点だと思う。

一般的なWebシステムであれば、
mysql = infra レイヤの概念 になると思うが、
phpMyAdmin のような MySQL の Web GUI を作成する場合を考えると、
domain にも MySQL の概念が出てくる可能性が高い。

例えば、
GUI上でテーブル名を入力すると、
MySQLに CREATE TABLE を発行し、
テーブルを作成することができる「テーブル作成機能」があるとする。

プロジェクトルートに table というパッケージがあった場合、
それは domain としての table なのか?
それとも infra としての table なのか?

プロジェクトルートに sql というパッケージがあった場合、
それは domain だろうか?
infra だろうか?

地味に分かりづらくなってしまう。

明示的に domain, infra というレイヤを用意してあげれば、
以下のようにそれぞれを分かりやすく管理することができるだろう。

/domain/table
/infra/table



そもそも domain として技術の概念を扱うシステムはレイヤ構造に関係なく、
管理が難しいものだと思うが、
goddd のレイヤ構造だと
より複雑になってしまうかもしれない。

こういった懸念はあるものの、
goddd はそのケースに該当しないので、
明示的なレイヤ構造は不要と判断したのかもしれない。

また、レイヤが明示的に存在しないことによって、
domain service と application の判別が難しいという問題もある。

例えば、
booking.Service は application で、
routing.Service は domain service になる。

両方共 interface になっており、どう使われるかで判別する必要がある。

仮にドメイン(業務)を理解していたとしても、
「業務上 booking は application だよね」というように判断するのは難しそう。

レイヤ間の循環参照に気をつける必要がある

明示的なレイヤ構造が存在する場合は
レイヤ間の依存関係は単一方向に保たれることが保証される。
つまり、レイヤ内の循環参照に気をつければいい。

goddd の場合は明示的なレイヤが存在しないので、
プロジェクトルートに置かれている各パッケージが、
どのレイヤに相当するものなのかを理解した上で、
依存関係を管理する必要がある。

例えば

cargo は domain なので、どこにも依存しないが、
booking は application なので、
cargo, routing に依存しても問題ない。

みたいな感じで各パッケージと相当するレイヤを紐付ける必要がある。
さすがにパッケージ名でこれを判断するのは厳しいかな・・・。

なので、
レイヤが存在しない前提で、
それっぽく依存関係が単一方向になっていればOK という感じに妥協することになるかもしれない。

つまり、ドメイン相当のパッケージがインフラ相当のパッケージに依存していても、
単一方向依存であれば許容するということ。

MySQLではなく、MongoDBを利用している

DDD本のサンプルは Hibernat + MySQL を利用しているが、
goddd は MongoDB を利用している。

Hibernat は Java の ORM で、
以下のように XML でモデルとDBテーブルのマッピングを定義することができる。
https://github.com/citerus/dddsample-core/blob/3f87e2ddbb27b7c62f174ebbc4ee588f52875b7c/src/main/resources/se/citerus/dddsample/infrastructure/persistence/hibernate/Cargo.hbm.xml

どこまで柔軟にマッピングしてくれるかが分からないが、
これによって、モデルのデータ構造とDBのテーブル構造が乖離していても、
いい感じにマッピングしてくれるらしい。

ちなみに、増田さんは MyBatis を利用しているみたいだが、
これも柔軟なマッピングを重視した結果みたい。



一方 goddd は MongoDB を採用しているので、
struct を JSON にして突っ込んで終了だと思う。
マッピングに困ることはない印象。

IDDD本では MongoDB によるリポジトリ実装が紹介されていたりする。

MongoDBに対するトランザクションが未実装?

Reposiroty のトランザクションはレイヤ構造を考える時に面倒だったりする。

DDD本のサンプルは Java + Spring なので、
application に @Transactional というアノテーションを付けて
トランザクションを管理している。
https://github.com/citerus/dddsample-core/blob/3f87e2ddbb27b7c62f174ebbc4ee588f52875b7c/src/main/java/se/citerus/dddsample/application/impl/BookingServiceImpl.java#L33

一方、goddd はトランザクションを利用していない。

今回のサンプルでは
わざわざトランザクションを利用する必要がないと割り切ったのかな?

ここはちょっと分からない。

補完は便利

domain, infra などで扱う概念が、
そのままパッケージになっているので、
IDEとかの補完でレイヤごとの同じパッケージ名がズラッと出てきて、
よく分からん感じになることがなさそう。

地味に便利だなーと思った。

まとめ

あまり長々と書いてもしょーがないので、
一旦ここまでにしておきます。

goddd は
モデリング済みの Java 実装を golang に移植しただけなので、
goddd と同じレイヤ構造にする = DDD になる
というわけではありません。

goddd はレイヤアーキテクチャgolang 風にアレンジしたものにすぎないのです。

ただ、
goddd と同じレイヤ構造にする = DDD にならない
とも言い切れません。

DDD を実践するにあたって、
ドメインが他の概念(レイヤ)から隔離されていれば、
アーキテクチャの形は問わないはずです。

goddd のレイヤ構造でそれが担保でき、
チーム内の合意が取れていれば、
goddd を参考に DDD を実践することも可能だと思います。
*やりやすいかどうかは別問題ですが・・・

ここまでの説明で goddd が気になるようであれば、
各パッケージ間の依存関係を中心に一度目を通してみると面白いかもしれないですね。