読書メモ「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」

ドメイン駆動設計入門」を読み終えたので、感想を疑問に思ったことを残しておく。

www.amazon.co.jp

本書のコードは C# で書いてあるが、あえて Java に変更して記載する。

長文注意。

全体を通して

最終的に分かったのは、これまでやっていた DDD のほとんどが「軽量 DDD」であった、ということ。 加えて「画面に必要だから・・・」とか「検索条件的に・・・」という理由で SQL に条件やら集計処理をガンガン書いていくスタイル。 これをリポジトリに実装していたもんだから、「DDD のメリットとは?」という状態だった。

パターンを使えばいいってものじゃないが、リポジトリは集約の永続化と再構築をするもので、 細かい検索は本書にも書いてある「リードモデル」を使って実装するなど、実装の場所を決めてあげることが重要だと思った。

残念ながら今のプロジェクトは開発ツールを使っているため、ドメイン駆動設計を持ち込むのが難しい状況なのだが、 今後のプロジェクトで生かしていける知識が得られたと感じている。

おかげで半分飛ばしなが読んだエヴァンス本、IDDD 本を読もうという気になったw

著者の @nrslib さん、関係者の方々、いい本をありがとうございます!

以下、疑問に思ったことなどをまとめてみた。

P39 2.5.2 「不正な値を存在させない」

値オブジェクトを作成する動機として挙げられているもののひとつで、プログラム内に不正な値が存在しないようにするため、というもの。

本書では、例として「ユーザー名は 3 文字以上」というルールを上げている。

// NG:存在してはいけない値
String userName = "me";

この場合、値オブジェクト UserName を作成し、コンストラクターにて値をチェック (本書では「ガード節」と記載されていた。) し、不正なインスタンスが生成されないようにしていた。

public class UserName {

    private final String value;

    public UserName(String value) {
        // null はエラー
        if (value == null) throw new NullPointerException("value");

        // ユーザー名が 3 文字未満はエラー
        if (value.length < 3) throw new IllegalArgumentException("ユーザー名は 3 文字以上です。(value)");

        this.value = value;
    }

}

疑問に思ったのは、UI 側でも同じエラーチェックをしたい場合、どのようにルールを 1 箇所で管理するかどうか。

ドメイン駆動設計に関して似たようなパターンとしては「仕様」があるが、それを使って UI とドメインのエラーチェックを共通化できるのではないかと考えた、ただし、それはあくまで UI とドメインが同じプログラミング言語を使っているとき限るのかなと思った。

いまのプロジェクトの場合は、最終的にはデータベースに永続化するため、テーブル定義書に桁数やコード体系などをまとめているため、そちらを参照して別々に実装してしまうケースが多い。

また、主題とは関係ないが、C#ArgumentException は、どの引数への例外かを表すコンストラクターの引数が定義されているため、メッセージの構築が楽だなと思った。Java の例外クラスは使いにくい気がする。

P78 コラム「ドメインサービスの基準」

その処理がドメインサービスかどうかを見極める際に筆者が重要視していることは、ドメインに基づくものかそうでないかという点です。 「ユーザーの重複」という考えがドメインに基づくものであれば、それを実現するサービスはドメインサービスです。 (中略) もちろん、可能な限り入出力はドメインサービスで取り扱わないようにするという方針には賛成です。

この方針は私も賛成。もしドメインサービスにてリポジトリの参照が必要な場合、リポジトリインターフェースのみをドメイン層に定義するようにしている。

・・・と書きつつ、リポジトリインターフェースが散在するのを避けるため、ドメイン層にすべて配置することが多い。

P87 コラム「リポジトリドメインオブジェクトを際立たせる」

前項とも被る話題ではあるが、リポジトリインターフェースをドメイン層に配置することが多い。本書ではリポジトリドメインの概念ではないとあり、私の考えと異なるが、これは私の中でドメインとアプリケーションの境界が曖昧であるため、と考えている。

顧客とドメインの話しをする際、次のような会話がある。

  • 受注伝票をもとに在庫を確認し、在庫があれば出荷の手続きに入る。
  • 受注伝票は会計のためファイルに閉じておく。

さて、受注伝票をファイルに閉じておくのはドメインの知識かどうか、というところが曖昧だった。この本を読んでからは、ドメインの知識を「集約」として洗い出したあと、それを保管しておくという業務手順については「リポジトリ」として、ドメイン層ではない場所に配置したほうがいいのかと思うようになった。

P127 コラム「煩わしさを減らすため」

単純に記述数が増えることを嫌う開発者は一定数います。 そうしたとき取れる手段は、彼らに降りかかる煩わしさの肩代わりをするものを用意することです。 具体的にはドメインオブジェクトを指定すると、その DTO となるクラスコードを生成するツールを作るとよいでしょう。

ドメイン駆動設計や、レイヤードアーキテクチャをやると、レイヤー間のデータ転送に DTO を用意することが多い。 上手く行っているプロジェクトは、Excel ファイルなどからソースコードを生成するツールを用意していることが多いので、確かにそのとおりだと思った。

P157 6.6.1.「サービスは状態をもたない」

Register メソッドは sendMail の値によって処理が分岐します。 (中略) 状態がもたらす複雑さは多くの開発者を混乱させるものです。 状態をもたせる以外の方法を考えてください。

ユーザー登録処理の中で登録時にメール送信をするという処理を例に、メールを送信する / しないをサービスクラスのインスタンス変数 sendMail で切り替えるという悪い例を挙げている。

メール送信も、業務によっていくつかのパターンがあると思う。

  1. テスト環境ではメールを送信しないが、本番環境ではメールを送信したい。
  2. ユーザーの設定によってメールを送信する / しないを切り替えたい。

今回はユーザー登録であるため、上記 1 が該当すると想定した場合の実装を考えてみる。(登録前にユーザーごとのメール送信する / しないの設定はないだろうという想定。)

Spring Framework の場合、設定クラス (Configuration) を DI してそちらからメールを送信するか / しないかを切り替えるための設定を読み出す方法が考えられる。 ただしこの場合、インスタンス変数がグローバル変数に移ったようにしか見えないため、完全な正解ではない気もする。

環境が用意できるのであれば、登録処理イベントを発行し、それを受信した「登録メール送信コマンド」がメール送信をするという「イベントソーシング」にしてもいいと思われる。 メール送信に時間がかかったり、失敗したときにユーザー登録処理自体を失敗としたくない場合などは、この方法が有効だと思う。

P199 8.4.1. 「ユーザ登録処理のユニットテスト

テスト用のリポジトリがデータ保管先としているフィールドを外部から操作できるようにすることは、きめ細かい検索を可能にし、テスト用モジュールのり弁性を向上させます。 フィールドを無闇に公開することは避けるべきですが、通常利用されるのは IUserRepository であるため Store プロパティを操作はできません。

これは目からウロコ。 確かに、C# の場合は LINQ で、Java の場合は Stream でコレクションを自由に操作できるため、テスト用リポジトリのフィールド等を公開しておけばテストの煩わしさがなくなる。

フィールドは private であるべき、という固定概念が崩れて、柔軟に考えられるようになった気がする。 あくまで外部からの操作はすべてインターフェース経由、ということを考えると、テスト用リポジトリだけではなく、本番用リポジトリテストのために フィールドを公開するのは有効だと思った。

P220 9.4 「複雑な生成処理をカプセル化しよう」

ポリモーフィズムの恩恵に与るためにファクトリを利用する以外に、単純に生成方法が複雑なインスタンスを構築する処理をまとめるためにファクトリを利用するのもよい習慣です。

あまり意見は無いんだけど、筆者の Web+DB での特集にて、インスタンス構築 と、インスタンス再構築 をメソッドで分けて実装されていたのを思い出した。

この使い分けはいいなーと思った記憶があるが、本書では特に構築と再構築を分けて記載しているわけではなかった。 プロジェクトや規模ごとに異なるとは思うが、上記の分け方は分かりやすいなと思っている。

P230 10.3.2 「ユニークキー制約との付き合い方」

ドメインのルールを守るための具体的な方法としてユニークキー制約に頼ることは得策と言えません。 ではユニークキー制約はまったく使えないものなのか、というとそれも間違いです。 (中略) ユニークキー制約はルールを守る主体ではなく、セーフティネットとして活用されるべき機能です。

これはその通り。 ユニークキー制約の他、外部キー制約やチェック制約などいくつかあるが、どの程度ドメイン駆動設計と併用されているのだろうと気になった。

P277 12.1.3 「内部データを隠蔽するために」

最初にもっとも単純な一般的なアプローチとして挙げられるのがルールによる防衛です。 (中略) もうひとつのアプローチは通知オブジェクトを使う方法です。

この通知オブジェクトというのは知らなかったが、エヴァンス本などにも書いてあるのだろうか。

ざっくり書くと、集約に通知メソッドを用意し、通知オブジェクトのインターフェースを経由して内部データを通知してもらう ものを指す。 通知オブジェクトのインターフェースはドメイン側で用意するため、通知する側にて内容を制限することもできるし、 通知される側も、通知オブジェクトのインターフェースを必要なものだけ実装すればいい。 もしドメイン側で項目が追加された場合、インターフェース実装クラスで追加実装が必要となり、コンパイルエラーとなる。

なかなかいい仕組みなのではないか? もう少し詳しく知りたいが、通知オブジェクトで検索してもあまり出てこないな・・・。

P320 14.1 「アーキテクチャの役割」

開発者は「一事が万事」といった言葉に目を向けず、いつかリファクタリングをすべきタイミングが訪れる、という夢を信じがちです。 (中略) もちろんそんなときは訪れません。

心当たりがありすぎて耳が痛い・・・。

P354 コラム「ユビキタス言語と日本語の問題」

オブジェクト名、メソッド名などを日本語で記述する試みは弊社でもいくつか例がある。 読みやすいという意見が多いが、エディターの補完機能の恩恵を受けることができないのは辛い。

例えば「店舗」を「te」と入力した時点で候補を出してくれるような、日本語とローマ字を突き合わせて補完候補を絞ってくれるプラグインなどがあればすごくいいんだけど。 結局「tempo」で実装するのが入力のしやすさと読みやすさのバランスがいい。仕事や会社のメンバーを考えると、無理に英語を使う必要はないかな・・・。