30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

『WEB+DB PRESS Vol.113』の「体験 ドメイン駆動設計 モデリングから実装までを一気に制覇」を読んだ

ドメイン駆動設計(以下 DDD)に関心があるので読んでみた。
私のような初心者にも分かりやすい内容だったので、DDD に興味を持ったけど挫折した、という人は読んでみるといいと思う。

gihyo.jp

DDD に関心を持ったキッカケはよく覚えていて、今年の2月。
SPA のフルリニューアルを一人でやることになり、設計や技術選定について考えていた。
既存の SPA の出来があまりにもひどくて、毎日がとにかく苦痛で不愉快だった。その体験があったため、自分はちゃんとしたモノを作ろうという気持ちが強かった。
そこらへんの話は以下の記事にも書いた。

numb86-tech.hatenablog.com

その時期に出会った記事のひとつが、これ。

medium.com

この記事の内容そのものも参考になったが、この記事で触れられている DDD にも関心を持った。
詳しくは分からないが、DDD というものを使えば、もっといいものを作れるのではないか。そう感じた。

その後、ネット上の記事を読んだり、「わかる!ドメイン駆動設計 ~もちこちゃんの大冒険~」を読んだりした。
どれも有益ではあったのだが、断片的な用語や考え方しか得られていないような感覚があった。かといって、原典は難解な本として有名であり、今の私が読んで理解できるとは思えない。
上記の Vuex の記事のように、断片的、部分的に DDD の知見を使うだけでも十分に価値はあると思う。だが、DDD の全体像を頭のなかで上手く描けておらず、消化不良のような感覚を抱いたままだった。
「よく分からない」という意識をずっと抱えており、何より、実際のプロダクトにどう活かしていけばいいのか、イメージできなかった。

この特集を読んで初めて、プロダクトに活かせる、実践できるかもしれない、という感覚を抱けた。
少しは頭のなかに地図を描けたような気がする。知識や用語同士のつながりや関連性を自分なりに持つことが出来た。

説明や例えが分かりやすいし、具体的なソフトウェアを題材にした実践的な内容になっている。
何より、短くて、かつ、「一連の流れ」が描かれている。モデリングだけ説明されてもよく分からないし、アーキテクチャの話だけされてもそれを使う意図を掴めない。かといって全てを説明しようとすると難易度が急激に上昇する。
本特集は DDD を実践していく流れをコンパクトに説明しており、丁度よい分量になっていると思う。

以下、自分なりのまとめ。

ドメイン、モデリング、モデル

ソフトウェアは、何らかの問題を解決するために作られる。 その、解決しようとしている対象、それを取り巻く領域を、ドメインという。その領域に存在する事象や概念、物体などが、ドメインに含まれる。

だが、ドメインに含まれる全てが、ソフトウェアに必要なわけではない。ソフトウェアにとって重要かつ十分な知識や概念を抽出する行為をモデリングと呼び、成果物をモデルという。
以下の定義が分かりやすかった。

ここでは、「モデルとは、問題解決のために物事の特定の側面を抽象化したもの」と定義します。

ドメインの概念からモデリングして作ったモデルを、ドメインモデルという。

モデルは、問題を解決するために作るもの。だから、問題を解決できるのがよいモデル。理解しやすいモデルでも、問題解決に役に立たないなら価値がない。

DDD にはモデルファーストという考えがあり、まずコードではなくモデルを作ることから始める。

モデリングのやり方

モデリングには決まった方法はない。
本特集では、ドメインモデル図を作るという方法を紹介している。ドメインモデル図とは、簡易化したクラス図のようなもの。

モデリングをやる前にまず、スコープを定めて、モデリングの対象を限定しておく。

モデリングの説明で「オブジェクト」という言葉が出てきたが、これが何を指すのか分からなかった。プログラミング言語という文脈での「オブジェクト」は知っているが、モデリングという文脈ではそれは何を意味するのだろうか。
取り敢えず、「概念」とか「ドメインモデルの構成要素」のような意味で捉えた。
そして、モデリングとは、オブジェクトそのものやオブジェクト同士の関係性について、ルールや成約を定義していく行為、と解釈した。

モデリングの作業は一度で終わりではなく、何度も繰り返して、より適切なものにしていく。
オブジェクト同士の関係性を見直したり、オブジェクトを分割したり名前を変えたりすることで。

モデリングの過程で発見した言葉や概念は、コード、ミーティング、ドキュメントなど、プロジェクト内の全ての場所で使うことを目指す。
この言葉をユビキタス言語という。

ドメインモデルがある程度出来たら次は、ドメインモデルに対して集約の範囲を定義する。
集約とはオブジェクトのグループ分けのようなもので、全てのオブジェクトはいずれかの集約に所属する。
どういう基準でグループ分けするかというと、整合性を強く確保したいオブジェクトを、一つの集約にまとめる。そして、集約毎に、「親」となるオブジェクトをひとつ決める。そのオブジェクトを「集約ルート」という。
同じ集約に所属するオブジェクトは、必ず集約単位で取得や処理を行う。言い換えれば、ひとつのトランザクションにまとめる。集約ルートに状態管理の責務を負わせ、子を直接操作できないようにする。
そうすることで、集約内のオブジェクトの整合性を保つことが可能になる。

ドメインモデルをコードに落とし込む

モデリングのあとは、ドメインモデルをコードに落とし込む。

そのときにありがちな失敗が「ドメインモデル貧血症」。
これは、ドメインモデルを実装するためのオブジェクト(ドメインオブジェクト)が、ドメインに関する知識を持っていない状態になってしまうこと。
より具体的には、ドメインオブジェクトが単なるデータ構造体になってしまっており、それとは別に、処理を行うための機能を持ったオブジェクトが作られ、そちらにドメイン知識が書かれてしまっている状態。

DDD について積極的に情報発信している増田亨さんのこのブログ記事が、分かりやすい。

新訳版『テスト駆動開発』に学ぶオブジェクト指向設計 | システム設計日記

ドメインモデル貧血症について書かれた記事ではないが、この記事でいう「手続き型」の設計が、ドメインモデル貧血症に近いと思う。DDD なのにこのような状態になってしまっている場合は、見直す必要がある。

データを持つクラスと処理を持つクラスを分け、トランザクションスクリプトを開発するスタイル

そして以下のように、ドメインオブジェクトを単なるデータ構造体にはせずに、ドメインに関する知識を持たせるのが、在るべき姿。

「換算」という機能よりも、もっと基本的な部品である「ドル」や「スイスフラン」をどうオブジェクトとして表現するかからスタートしています。問題領域の関心事をオブジェクトで表現するという典型的なドメインモデルの開発スタイルです。

そして「処理を行うための機能を持ったオブジェクト」は、ドメインに関する知識を持たず、ドメインオブジェクトを使ったユースケースが記述されたものにしていく。

ドメインオブジェクトを実装する際は、正しい状態のインスタンスしか存在させないようにすることを意識する。
そのために、インスタンスを作成するメソッドや、インスタンスが持っている状態を変更するメソッドは、その原則が守られるように実装する。それと同時に、余計なメソッドを露出させずプライベートメソッドにしておき、インスタンスに意図しない変更が加えられることを予防しておく。

また、ドメインオブジェクトを適切に分割していき、本来持つべきビジネス知識のみを、持つようにする。
そうすることでオブジェクトの肥大化を防ぎ、責務の適切な分離が進むことで、可読性や保守性が高まる。

アプリケーションアーキテクチャ

上記のようなコードを実現するための指針として、アプリケーションアーキテクチャがある。
アプリケーションアーキテクチャとは、アプリケーション全体にまたがる設計のこと。アプリケーション全体をレイヤに分割し、レイヤ毎に責務を定義していく。

DDD は、特定のアプリケーションアーキテクチャと結びついてるわけではない。この特集では一例としてオニオンアーキテクチャを紹介している。
サンプルコードのリポジトリに、オニオンアーキテクチャの図が掲載されている。

github.com

依存関係は、下方向への一方通行になっている。そのため、ドメイン層はどこにも依存しない。
両端にある「コントローラ」や「リポジトリ」が、各レイヤーの実装。

DDD では、ドメインモデルを表現するためのパターンとして、以下の4つを定義している。

  • エンティティ
  • 値オブジェクト
  • ドメインサービス
  • ドメインイベント

ドメインモデルをオブジェクトとして表現するのは、エンティティと値オブジェクトの2つ。
両者の違いは以下を参照。本特集の執筆者の一人である松岡幸一郎さんによって書かれたもので、本特集にもほぼ同じ内容の説明が書かれているのだが、非常に分かりやすい。
DDD基礎解説:Entity、ValueObjectってなんなんだ - little hands' lab

ドメインサービスは、オブジェクトで表現するのが難しいものを表現するために使われる。

アプリケーションサービスは、ドメインモデル(が公開している操作)を使ってユースケースを実現する。

リポジトリは、永続化のためのインターフェイスを提供するもの。
インターフェイスの定義はドメイン層で行い、実装はインフラストラクチャ層で行う。
こうすることで、ドメイン層が、ドメイン知識以外の知識(DBやテーブル構造などに関する知識)について意識せずに済む。

とにかく、レイヤ毎の責務を守ることが大切。
例えばドメインに関する知識については、ドメイン層から外に漏れ出さないようにする。もしアプリケーションサービスにドメイン知識が書かれてあれば、その実装はおかしい。ドメイン層以外がドメイン知識について知っていてはいけない。アプリケーションサービスには、ドメインオブジェクトを使って何をするかという、抽象的な記述しか書かれていないはず。

先程のリポジトリの例もそうだが、それぞれのレイヤの責務や役割を意識し、コードが、あるべきところにある状態にすることが大切。
そうすることで、コードを理解するのが楽になるし、コードの変更も安全に行えるようになる。

重要なのは、最初から完璧な設計を目指すのではなく、継続的に改善を繰り返していくこと。

DDD が目指すもの

ドメインを適切にコード(ソフトウェア)に反映させることで、よりよく問題解決できるようにすること。ならびに、そういう状態を維持できるようにすること。
これが DDD の目指すものだと理解した。

ドメインからモデルを抽出して、それをコードに落とし込む。そのため、モデルを媒介としてドメインとコードは連動している。

しかし、ドメインは様々な理由で変化していく。コードは、その変化に対応しないといけない。
そのため、コードを変更するコストを下げることが大切になる。変更のコストが高いコードは、ドメインの変化に対応することが出来ず、ドメインとコードが乖離していってしまう。

そのために、アーキテクチャがある。変更に強い実装にするための指針として、アーキテクチャがある。
そしてそれは、アーキテクチャ自体には意味がないということでもある。モデルを適切にコードで表現するための指針としてアーキテクチャがあるのであり、アーキテクチャを遵守することそのものには意味がない。
アーキテクチャを活用して上述の「コードがあるべきところにある状態」を実現できれば、ドメインを正しく表現しており、かつ、ドメインの変化にも追従していきやすい、そんなコードになっているはず。

また、DDD は変化を前提にしており、そして継続的な改善が重要であるため、テストコードの存在は不可欠と言える。テストコードがなければ、それだけで変更コストが非常に高いものになってしまう。