ドメインレイヤの実装をサブドメインごとに管理する

自分は以下の書籍で各レイヤ構造に対する実装を Go で解説しているが、 書籍を書いてからも異なるパターンを色々と試行錯誤していた。 pospome.booth.pm

書籍内ではユーティリティ系の実装を配置するパッケージとして "コアレイヤ" というレイヤの話があるが、 それを廃止してドメインレイヤ内にサブドメインごとにパッケージを配置した方がよいかもしれないと思った。 コアレイヤの存在がきっかけとなったが、 最終的には "ドメインレイヤの実装をサブドメインごとに管理する" という考えに落ち着いた。

具体的なパッケージ構成について

ここでは具体的なパッケージ構成について説明する。 上記の書籍内に乗っているパッケージ構成は以下である。

root
|-- presentation
|-- usecase
|-- domain
|-- infrastructure
|-- core

それを以下のように変更するという話である。

root
|-- presentation
|-- usecase
|-- domain
|          |-- core
|          |-- support
|          |-- util
|
|-- infrastructure

ポイントはドメインレイヤ直下の core, support, util という3つのパッケージである。 これらはそれぞれ実践DDDにおけるコアドメイン、汎用サブドメイン、支援ドメインという用語に対応する。 汎用サブドメインと支援ドメインサブドメインの種類である。 "ドメインレイヤの実装をサブドメインごとに管理する" というのは、 コアドメイン、支援ドメイン、汎用サブドメインごとに core, support, util という3つのパッケージを作るという意味である。 それぞれの意味はググって欲しい。

それぞれのパッケージの役割を説明する。

core にはコアドメインに属する実装を配置し、support には支援サブドメインに属する実装を配置する。

core or support 判断はコアドメインかどうかポイントになる。 これはシステムにおいてそのドメインをどの程度重要視するかに依存するので、 例えば、とあるソーシャルゲームでは対戦機能をコアドメインとみなし、 ガチャ機能を支援ドメインとみなすかもしれないが、 別のソーシャルゲームでは逆になることもある。

util には汎用サブドメインに属する実装を配置する。 例えば、ソーシャルゲームにおける DB接続の管理、配列を加工する便利関数、メール送信処理、エラーハンドリング、認証などである。 モノリスなサーバサイドアプリケーションだと汎用サブドメインに属する実装が多くなるだろう。 コアドメインに対応するパッケージが core なのでややこしいが、この util パッケージが前述したコアレイヤ相当のものになる。

汎用的な実装をドメインレイヤに配置する違和感

汎用的な実装が domain パッケージ内にあることに違和感があるかもしれないが、 そもそもドメインというのはソフトウェアを適用する対象のことなので、 DB接続管理やメール送信もそれぞれ "DB", "メール" というドメインに属する実装である。 自分はこの考えを前から持っていて、各実装に "ドメインである", "ドメインではない" という違いはなく、 ソフトウェアを適用する実装すべてがドメインであり、それらに優先順位や種類があるだけだと思っている。

例えば会計ソフトを作るとき、会計に関する実装だけがドメインというわけではなく、 会計に関する実装は "会計" というドメインに属するものであり、 DB接続は "DB" というドメインに属するものである。 そして、会計ソフトという性質上、 会計ドメインに属するものがDBドメインに属するものより重要なものとして扱われるケースが多い。 これはDBに関する実装に注力しても会計ソフトとしての機能は増えない(= 会計ソフトの価値が向上しない)からである。

こういった考えにより、汎用的な実装をドメインレイヤに配置する事に違和感を持つことはなくなった。

util パッケージと infrastructure パッケージの違い

util パッケージと infrastructure(クリーンアーキテクチャにおける Frameworks & Drivers)パッケージの違いについてだが、 infrastructure はあくまでも差し替え可能な実装を配置する場所として定義し、 domain パッケージなどでインターフェースが定義されている想定である。 インターフェースを必要としない(実体に依存してもよい)実装は util に配置する。 なので、例えばリポジトリパターンの実装であれば、従来通り domain パッケージ内にインターフェースを配置し、 infrastructure パッケージにそのインターフェースの実装を配置することになる。

ドメインの優先順位と開発

ドメインレイヤを core, support, util に分けることによって、各ドメインに優先順位がつけられる。 具体的には core が一番重要な実装であり、util が一番重要ではない実装になる。 これによってコードの品質やテストカバレッジの高さにも優先順位を付けることができる。 core は重要な実装なので、リファクタリング回数やテストカバレッジを高く保つ方がよいだろう。 コードレビューもしっかりやった方が良さそうである。 一方で、util は・・・頑張るに越したことはないが、それほど頑張らなくてもいい実装とみなすことができる。 例えば "util はテストカバレッジが低くても main ブランチに merge することはできる" という CI 設定にしてもいいかもしれない。

スタートアップに限らず、サービス開発は時間との戦いになることが多い。 実装時に注力できない部分もある一方で、 開発期間が長くなればなるほどコードの品質やテストコードの充実さが開発効率に対して有利に働く場面も多い。 実装に使える時間は有限なので、 最終的にすべてのコードの品質やテストカバレッジを高くするとしても、 明確に優先度を付けて順番に対応する基準があって困ることはないだろう。

上の方でも少し触れたが、core, support の判断は難しい部分がある。 人によって判断にバラつきがあるだろう。 しかし、それはシステムが価値を発揮する領域の認識合わせが難しいという根本的な問題が存在するからである。 core, support の判断を議論することによって、 エンジニア間でソースコードレベルのコミュニケーションによって認識を合わせることができると考えると、 それはそれでメリットがあると感じる。 認識合わせのためにエンジニアとドメインエキスパートと会話する機会も増えるかもしれない。 core, support の判断がエンジニア間で一貫している場合は、 しっかりとコアドメインを認識できているという目安になる。

実は以前まで "core, support の判断は難しい部分がある" という懸念から「ちゃんと判断できるかどうか分からないし、判断に時間かけるくらいなら core, support とか分けなくていい」と思っていたのだが、 実際の組織開発をリードしていく過程で、この考えを改めることになった。

注力するドメインの変化と実装の移行

注力するドメインというのは時間とともに変わっていくものである。 例えば "今まで支援サブドメインだったけど直近1年は力を入れて機能を増やす" というケースもあるだろう。 その場合に今まで core に配置してた実装を support に移して、 注力するドメインに関する実装を core に移すのかというと、その必要はない。 注力するドメインがコアドメインでないケースがあったとしても、 ソフトウェアにおいて一番価値を発揮する領域が変わらない限り、core, support の実装をそれぞれ移す必要はないと考えている。 例えば、会計ソフトにおいて、「今期はグラフ表示に注力する」という方針になったからといって、会計というドメインの実装を support に移す必要はない。 逆に "一番価値を発揮する領域" が変わった場合は、ドメインの優先順位が変わったという一大事なので、 むしろ support 内の実装を core に移した方がいいだろう。

まとめ

特に無いです。 (´・ω・`)

今回の記事の内容のようにアプリケーションアーキテクチャに興味のある方は楽しめるチームだと思っているので、 もし興味があれば連絡してください。