【読書感想ブログ】ドメイン駆動設計(モデリング/実装ガイド)

参考図書

little-hands.booth.pm

設計なんもわからん

みなさん開発してますか?開発してるとぶち当たるカベがありますよね?

そうです設計です。

「このシステムどうやったら奇麗に作れるんだろう?」と日々思い悩みながら

あーでもないこーでもないと手探りで開発を進める日々ですよね?

一昔前であれば「デザインパターン」を覚えたり、GitHubの人気リポジトリのコードを

真似したりするのが主流でしたが、最近はドメイン駆動設計(通称:DDD)なるものが

巷で噂になってますよね?

私がDDDという単語を耳にするようになってからもう数年たちます。

なにやら難しそうだな~という感想を持ち幾星霜。いよいよ重い腰を上げて勉強してみようと思った次第です。

ドメイン駆動設計とは?

ドメイン駆動設計を説明するためにまずは「ドメイン」について説明しなければならないでしょう。

ドメインと聞くと、hatena.ne.jp のようなものを想像しますがこれではありません。

ドメインとは、「ソフトウェアが解決しようとしている問題の対象領域」のことを指す言葉です。

ドメイン自体は英語で「領域」とか「分野」なんて訳され方をするので単純に「問題の対象領域」と思っておけばいいかと思います。

そして、ドメイン駆動設計とは

ドメインに対してモデリングによってソフトウェアの価値を高めることを目指す開発手法 のことを指します。

モデル

モデリングによってソフトウェアの価値を高める」ということですが、じゃぁ「モデリング」ってなんぞ?という話になりますよね?

モデリング」はモデルを作成することです。「モデル」とは、問題解決のために物事の特定の側面を抽象化したもの になります。

ようは、家を作る前に設計図を引きますよね?その設計図のことをモデルという言葉で表現してると思ってもらえばいいと思います。

良いモデルとは?

「設計図ってことは外部仕様書や詳細設計仕様書ってことだろ?そんなのとうの昔からやっとりまんがな」という人もおられるかもしれません。

でもその仕様書、本当に役に立ってますか?

良いモデルとは 問題解決ができるモデルのこと をいいますが、その仕様書で問題解決できているならよい仕様書という事だと思います。

もし、そういう仕様書を作成できていない、もしくは今の仕様書に不満がある、もっというと仕様書なんて書いてなかった。。。という人にはこの本は役に立つ情報が乗っているかもしれません。

良いモデルを作るには

良いモデルを作るコツがあります。それは

  1. ドメインエキスパートと会話し、ドメインについての理解を深めモデルを作成
  2. そのモデルを元にソフトウェアを作成
  3. 運用してみて気づいた問題を再度ドメインエキスパートと会話しモデルを改善
  4. 改善したモデルを元にソフトウェアを作成
  5. 運用してみて問題に気づく→3に戻る

というサイクルを回すことです。

ドメインエキスパートとは、ドメイン(問題領域)に詳しい人のことです。

採用管理アプリなら人事担当者とかのことですね。

はじめは設計担当者にドメイン知識がなくともエキスパートに聞きながら作成することで

大きな間違いを犯すことなく開発が進められるというメリットがDDDにはあります。

なので、できるだけサイクルは小さく早く回すのがよいでしょう。

アジャイルとの親和性

この作成→改善のサイクルを回すという流れはアジャイルととても親和性が高いです。

というのもこのDDDの提唱者のエリックエヴァンスはアジャイルを意識してこの設計手法を考えているからですね。

なので、開発プロセスアジャイルを取り入れるとDDDがやりやすくなるかもしれません。

ユビキタス言語

DDDの設計アプローチの一つに、「発見したモデルの言葉をすべての場所で使う」というものがあります。

例えば「商品」というモデルがあった時にそれを開発者だけではなくビジネス側の人間とも同じ言葉として使用するということです。

先ほどの「ドメイン」に複数の意味があってはならないということですね。

ある人はDDDの「ドメイン」を想像しているけど、ある人はURLの「ドメイン」を想像している。

その状態で会話をしてもいわゆる アンジャッシュのコント状態 になります。

なので、使用する言葉は関係するすべての場所で同じ認識をもって使うことが重要です。

それはコード内でも同様で、「商品」というモデルはコード中でも「商品」として扱います。

コード内に日本語が使えないのであれば、対応表を作成し、「商品」はコード上では「Goods」と表現することを明示してあげることが重要です。

そういう意味では英語圏の人たちは対応表を作成しなくてもいいのでDDDを取り入れやすいかもですね。

ユビキタス言語が必要な理由

DDDではモデルを何度も継続的にアップデートします。

そのアップデートの中でモデルとコードが乖離してしまうと変更に時間がかかってしまいます。

そしてバグも埋め込まれやすくなります。

そこで設計したモデルをそのままコードに落とし込めれば認識のずれがなくなり変更に強いソフトウェアとなります。

なので、極力モデルをそのままコードに落とし込むことを心がけましょう

DDDを実装するためのアーキテクチャ

DDDを実装するためのアーキテクチャは世の中に色々ありますが、

作者がオススメしているのは「オニオンアーキテクチャ」です。

DDDを行うための必要最小限のレイヤのみで構成されていながら

強力なアーキテクチャとなっています。

モデリング

作者はこの書籍の中でユースケース図とドメインモデル図を使ってモデリングを行っています。

これは作者の YouTube の中で紹介されている sudoモデリング の構成要素の一つです。

この書籍が出てから約2年後の YouTube で紹介されている sudoモデリング のほうが

より良いモデリング手法となっているのは明らかなので、そちらを参考にされたほうが

もしかするとよいかもしれません。約10分() の動画なので、サクッとみられてよいですよ (^^)

↓↓↓ 作者の YouTube 動画はコチラ ↓↓↓

さようなら軽量DDD。10分でわかるドメインモデリング - ドメイン駆動設計 - YouTube

DDD固有のモデリング手法

集約

集約とは「必ず守りたい強い整合性を持ったオブジェクトの集まり」のことです。

集約を設計・実装する時のルールとして以下の2点があります。

  1. 強い整合性の確保が必要なオブジェクト群を1つの集約にする
  2. トランザクションを必ず1つにする

集約オブジェクトを扱う時親となるオブジェクトを集約毎に1つ決めます。

その決められたオブジェクトを「集約ルート」と呼びます。

また、集約は必ず集約単位でリポジトリから取り出し、格納する時も集約単位で行います。

こうすることで整合性が保証します。

どの単位で集約を決めるかというのは機械的に決めることはできず

システム全体のバランスを考えて決めます。

集約は整合性を「強く」確保したいものを1つの集約とします。

整合性を求める全てのオブジェクトを1つにするわけではないので注意が必要です。

集約の境界の決め方はある程度経験と知識が必要になってくるので

一度決めてもおかしいと思ったらモデルを修正してより良い方向に改善し続けるサイクルを回すのが良いと思います。

また、トランザクションの範囲も考慮しながら集約の境界を決めるとよいでしょう。

集約を大きくしてしまってトランザクションのロックも不必要に広くとってしまうようなことになると

システム全体の性能劣化も起こってしまいます。適切なトランザクションの範囲となるように

集約のサイズを決定することが重要です。

境界付けられたコンテキスト

境界付けられたコンテキストとは「特定のモデルを定義・適用する境界を明示的に示したもの」です。

例えば「商品」というものをイメージする時、販売部と配送部ではイメージが異なります。

販売部では「商品」は販売するものであり、配送部では運ぶ対象です。

販売部で欲しい情報ややりたいことがそのまま配送部でも必要だとは限りません。

それぞれにやりたいこと、欲しい情報があったとして、それを一つのクラスで表現すると

複雑で混沌としたクラスになりがちです。

そうするよりも販売部用の商品クラスと配送部用の商品クラスを分けて実装したほうが

クラス自体の複雑さは抑えることができます。

設計の基本原則(高凝集・低結合)

コードを高凝集・低結合にした場合のメリットとして以下のようなものがあります。

  • コードを読んで理解しやすくなる
  • コードを修正・拡張しやすくなる
  • 修正時にバグを埋め込みにくくなる
  • 同じコードを別の場所で再利用しやすくなる
  • テストを実施しやすくなる

昔、私が新入社員のころ、会社で言われていた「みんなが使える部品的なコード」を集めて

開発効率を上げようという取り組みを思い出しました。ようは「コードのモジュール化」を行いましょう。ということですね。

凝集度

凝集度とは「責務・データ・ふるまいの関連の強さ」の尺度です。

「このクラスは何をするクラスなのか?」という問いに対する答えがすべてのクラスで明確になっていれば高凝集であると言えるでしょう。

それをなすためには日ごろから責務について設計の段階で常に考えておく必要があります。

結合度

結合度とは「複数のクラス動詞が依存している度合い」の尺度です。

インターフェースや依存性の注入によって結合度を下げることができそうです。

オニオンアーキテクチャ

※ 図は 新卒にも伝わるドメイン駆動設計のアーキテクチャ説明(オニオンアーキテクチャ)[DDD] - little hands' lab より引用

オニオンアーキテクチャ

の4層からなるアーキテクチャです。

レイヤードアーキテクチャの問題点をインフラ層とドメイン層の依存関係を逆にすることで解決しています。

プレゼンテーション層

ここはクライアントとの接点となり、エンドポイントの定義や Http Request で渡された値とユースケース層に渡す値とのマッピング、入力値の一部検証を行う責務があります。

ユースケース

ユースケース層はドメインオブジェクトの生成・使用・永続化依頼を行います。

ドメインオブジェクトからプレゼンテーション層に渡す値の変換もこの層の責務です。

ユースケース層では「何をしたいか (What)」を書き、「どのように実装を実現するか(How)」はドメイン層に記述します。

ユースケースがそのまま記述できていることが理想です。

ドメイン

ドメインモデルの知識を対応するオブジェクトに記述します。

常に正しいインスタンスしか存在させないことが重要です。

そうすることで、常に整合性が保証されます。

そのような実装にするには以下の2点を行います。

  1. 生成条件の強制(デフォルトのコンストラクタを使わずに、値が保証されるコンストラクタを定義したり、ファクトリメソッドを定義します)
  2. ミューテーション条件の強制(オブジェクトの整合性を保証して変更できるメソッドのみ公開します)

インフラ層

インフラ層はリポジトリを実装したり、ドメインオブジェクトの永続化・検索の実装が責務となります。

外部入出力とのコネクション

外部とのコネクションはプレゼン層やインフラ層にアダプタクラスを作成してアダプタクラスを経由してクライアントやデータベースとやり取りを行います。

テスト

DDDは繰り返しモデル・コードがアップデートされます。そのためテストコードは必須です。

ユースケースドメインオブジェクト単位でテストを書き、CIを回すのが理想です。

境界付けられたコンテキストの実装

先ほど境界付けられたコンテキストは個々のグループでクラスを持つという話をしました。

個別にクラスを持つということはデータの同期が必要になるということです。

それを実現するために、グループ毎にアプリを作成しインフラ層のアダプタ間を接続して行う方法があります。

※ 図は『ドメイン駆動設計 モデリング 実装ガイド V1.0.3 P64』より引用

1アプリでやりたい場合は、パッケージで切り分けるという方法もあります。

ドメイン層の実装

ドメインモデルを表現するもの(ドメインオブジェクト)は以下の3つが存在しています

  • エンティティ
  • 値オブジェクト
  • ドメインイベント (この本の範囲外)

ドメインオブジェクトを使用するものには主に以下のものがあります

値オブジェクト

値オブジェクトとは、世の中にある値をオブジェクトとして表現しているオブジェクトです。

たとえば「日付」だとか「金額」だとかがそれにあたります。

Date型やint型がすでにあるプログラミング言語であっても

業務内容によって制限があったりルールがあったりする場合は別途その用途に合った型を

値オブジェクトとして定義してあげることが重要です。

エンティティ

エンティティは値オブジェクトと似ていますが、同一性の判定は識別子で行われ、不変性も可変です。

値オブジェクトは同一性の判定を属性値で行い、普遍性は不変です。

ドメインサービス

ドメインサービスとは「モデルをオブジェクトとして表現すると無理があるもの」を表現するときに使用されます。

例えば、メールアドレスの重複チェックがそうです。複数のオブジェクトを操作してチェクする必要があるので

ドメインオブジェクト単体ではモデルを表現できません。

ただし、ドメインモデルは極力エンティティ・値オブジェクトとして表現するようにして

どうしても避けられない場合にのみドメインサービスを使うようにしましょう。

ドメインサービスはどうしても手続き型の実装になりがちで1サービスがファットなクラスになってしまいます。

リポジトリ

リポジトリとは「集約単位で永続化層へのアクセスを提供するもの」です。

リポジトリに渡すもの・返されるものは 必ず 集約ルートのエンティティになるように設計・実装します。

リポジトリを設計するときはリポジトリを List のように扱います。

List に具体的なメソッドが生えていないように

リポジトリにも抽象的なメソッドのみを生やし

ドメイン知識は別のクラスで実装するようにしましょう

ユースケース

ドメイン層が整合性を保証できるメソッドのみを公開していれば

ユースケース層はそれを組み合わせて安心してユースケースを実現できます。

この組み合わせを行うことがユースケース層の責務になります。

ユースケースからの戻り値クラス

ユースケース層からプレゼンテーション層に返す値の型は

専用の戻り値クラスに詰め替えて返しましょう

ドメインオブジェクトをそのままプレゼンテーション層に渡してしまうと

往々にしてドメインオブジェクトにプレゼンテーション層のメソッドが生えてしまって

責務違反になってしまいます

CQRS

CQRSは Command Query Responsibility Segregation の略です。

CQRS は「参照に使用するクラスと更新に使用するクラスを分離する」というアーキテクチャです。

※ 図は『ドメイン駆動設計 モデリング 実装ガイド V1.0.3 P83』より引用

更新系のモデルはドメインオブジェクトをそのまま使用し

参照系のモデルは特定のユースケースに特化した値の型を定義し、

その値を取得する為のサービスも独自に定義します。

プレゼンテーション層

プレゼンテーション層はクライアントトアプリケーションの入出力を実現します。

HTMLテンプレートやルーティング設定もこのレイヤの責務です。

クライアントとの入出力に関するすべてのことはこのレイヤの責務と思っておけばいいでしょう。

WAFに依存する内容をこのレイヤに閉じ込めておければ

ユースケースレイヤ以外は依存なく実装が可能です

表示書式に関することもこのレイヤの責務で

"1,000円" という文字列を 1000 という数値に変換するのがこのレイヤです。

まとめ

DDDについて今までは新しい設計手法で、理解するのにすごく敷居が高いというイメージでしたが

この本を読んでDDDのエッセンスが知れた気がしました。

また、CQRSのような更新系と参照系を分離してクラス化する方法や

インフラ層とドメイン層の依存関係を逆転させるという方法が目からうろこで

この本を読んでよかったなと思えた点でした。

この本を軸にして別のDDD本も読んでDDDの理解を深めたいと思いました。