「ドメイン駆動設計 モデリング/実装ガイド」を読んだ

little-hands.booth.pm

読んだきっかけ

自分の書いているコードに対してある程度「これで問題ない」という確信が持てない状態がそれなりにストレスになると気付いたので読んだ。

今の職場に来てから、新機能の内部設計および開発を一任されるなど、以前よりも粒度の大きなタスクを担当することが増え、それに伴ってコードを書く量も増えた*1。 今まであまり本腰を入れて設計を勉強してこなかったこともあり、動きはするしテストもあるが複雑で保守性のよくなさそうなコードができあがりがちだった。 今にして思えば、アプリケーション層にドメインオブジェクトが持つべき知識が記述されドメインモデル貧血症を起こしていたし、アプリケーション層で外部APIにアクセスするクライアントやレスポンスのパースを行うクラスまで実装してたので、アプリケーション層がごちゃごちゃになっていた。 何度かリファクタを行うが、そもそもあるべき状態がわからないので根本的な解決もできなかった。 そうこうしている内に自分の書いたコードが全くもって駄目だという事実を突き付けられた日はどっと疲れが溜まっていることに気付き、本腰を入れて設計と向き合うことにした。

ただ、保守性の高いコードを書くというのはDDDのもたらすものの本質ではなかったのだが...。

なぜDDDか?

今回DDDに着目したのは、現在携わっているプロジェクトが

  • Play FrameworkがDIコンテナを採用しているためDBアクセスだけはリポジトリとして定義されている
  • プロジェクトのディレクトリ構成を見るとルート直下にdomain, infrastructureというディレクトリがある
  • domainの中に(何も表明やメソッドが定義されていないものの)ドメインオブジェクトになるべきデータクラスが定義されている

など、戦術的設計をいくらか実践しようとした痕跡があり、ここから改めて戦術的設計を導入していくことは全くの不可能ではないと感じたのが大きい。 また、アプリケーションが扱っているドメインも十分複雑性があり、リターンを十分得られないということも無いだろうと考えた。

なお、私はドメインエキスパートを巻き込み、チーム全体がユビキタス言語で会話し始めるまでは軽量DDDだと思っているが、残念ながらその状態への道のりは遠いと言わざるを得ず、まずは軽量DDDを目指していくことになる。

ドメイン駆動設計入門」と比較して

DDDに関しては以下の本を過去に読んだことがあった。

www.shoeisha.co.jp

しかし、途中で今説明されている内容がDDDの全体の中でどのような位置づけになる話なのかということが脳内で整理できず、読み進めてもいまいち知識が身についていないように感じ、積読してしまった。 「ドメイン駆動設計 モデリング/実装ガイド」はこの本とは逆にDDDに登場する概念をざっくり見ていく本で、こちらを読んだあと「ドメイン駆動設計入門」に戻ることで内容がより整理された形で頭に入ってきているのを感じている。 加えて、後述する理由で実際のプロジェクトに戦術的設計を適用していきやすくなるため、私としては「ドメイン駆動設計 モデリング/実装ガイド」から読むのを勧める。

感想

ドメインモデルとは何か、DDDはドメインについて詳しい人と話しドメインモデルを改良し続けていく手法であること、そのためにドメイン層は他のレイヤーに依存してはいけないこと、各レイヤーの役割とその中に何が含まれるかがよくわかる本だった。 「ドメイン駆動設計入門」についての部分で書いたように最初に各レイヤーの役割について押さえておくことは、その後DDDの学習を続けていく際に「今どの部分の話をしているのか」を意識して頭の中に配置していけるようになることにつながる。

また、この本を読んでからコーディングしていて気付いたが、各レイヤーの役割を知ることで「何をこのレイヤーに入れ、何を入れないか」という判断基準を自分の中に構築することができるという点も大きい。

たとえば、ScalaRDBとやりとりを行うためのライブラリの1つにScalikeJDBCというものがある。 ScalikeJDBCでは、たとえば以下のようなカラムを持つusersテーブルがあったとき(データ型は省略)

  • id
  • name
  • email
  • created_at

以下のようなクラスやオブジェクトをScala側で定義することでSQLを書くためのDSLがusersテーブルに対して使えるようになる。

import scalikejdbc.SQLSyntaxSupport

case class User(id: Long, name: String, email: String, createdAt: LocalDateTime)

object User extends SQLSyntaxSupport[User] {
  override val tableName = "users"
}

ではこのUserクラスとコンパニオンオブジェクトはどのレイヤーに置くべきなのか? 私が現在携わっているプロジェクトではドメイン層にいずれも定義されていたのだが、ドメイン層のコードが特定のライブラリに依存しているのはおかしいので、コンパニオンオブジェクトの方のUserは明らかにドメイン層に置いてはいけないとわかる。 DBとのやりとりに関係するオブジェクトなので恐らくインフラストラクチャ層に置くのが正しいかと思われる。 ではUserクラスについてはドメイン層に置いても問題無いか? 仮にこのUserクラスをドメインオブジェクトとして流用した場合、ドメインオブジェクトがusersテーブルの構造に依存してしまう。 なので、このUserクラスはドメインオブジェクトではなく、あくまでusersテーブルのレコードを表すクラスとしてインフラストラクチャ層に置くのが望ましいのではないか?

このようなことを「ドメイン駆動設計 モデリング/実装ガイド」を読んでから考えられるようになった。 実際にプロジェクトに対して戦術的設計を適用していく際に逐一誰かに聞くというのはあまり現実的ではないので、これは私の中でかなり大きかった。 自分の中にこういった判断基準を構築するには、ボトムアップよりもトップダウンな入門書の方が向いているのではないかと思う。 この点においてはDDD入門書としてよく挙げられる「ドメイン駆動設計入門」よりもこの本は優れている。

内容もスマートにまとまっており数日もあれば読破できるので、総じて、DDD入門の最初の1冊としてよい本ではないかと感じた。

*1:主に使う言語がRuby/RailsからScala/Play Frameworkに変わり、どうしても書かなければいけないものが増えたという事情もある