Java
オブジェクト指向
DDD
設計
ドメイン駆動設計

DDDに関する質問にバシバシお答えしました [ドメイン駆動設計]

image.png

先日、メディアマックスジャパン様(以下、MMJ様)にお邪魔してドメイン駆動設計勉強会を開催してきました。そちらで質疑応答セッションがあり、実際にドメイン駆動設計で開発をしだしたタイミングで出てきた具体的な疑問について色々お答えしました。
おそらく多くの人が同じような疑問を持たれそうな内容だったので、MMJ様の許可を得てこちらでも紹介したいと思います。

コンテキストの分け方について

DBを複数コンテキスト共通でつかっていいのか?
スキーマわけなくていいのか?

コンテキストごとにスキーマは最低限分けることをオススメしています。詳細は以下の記事をご参照ください。

境界づけられたコンテキスト 実装編 - ドメイン駆動設計用語解説

機能ごとに切る?ユーザ種別ごとにコンテキストわけるべきなのか?
コンテキストの切り方が正しいかどうか、どうやって判断すればいいか?

コンテキスト設計は、従えば作れるようなフローチャートはありません。実際には概念的な観点と実装的な観点両方から考えていく必要があります。ヒューリスティックなアプローチとして、以下のような複数の観点から検証していくのが良いかなと思っています。

  • モデルの意味を区切る領域として適切か。一つのモデルが複数の意味を持っていないか。
    • 文化的境界や組織的な境界はヒント
  • コンテキスト(解決領域)より、ドメイン(問題領域)の分け方として適切か
  • アプリケーションの切り方として、実装イメージが湧くか
    • マイクロサービスの切り方として考えると良いです
  • チームサイズが適切か
    • 経験則としては6-8人が最大と言われているそうです)
  • うまく名前がつけられるか
    • 名前がつけられないということは、概念の切り方が間違っている可能性がああります

別コンテキストと同じモデルを共有していいか?

「同じモデル」は共有しません。

境界づけられたコンテキストの定義が「一つのモデルを適用する明示的な範囲」であるからです。複数コンテキストで共有するとこの定義に反してしまいます。

ただし、これは「同じ名前のオブジェクトが複数のコンテキストに存在してはいけない」ということではありません。

「同じ名前だけど振る舞いの違うモデルを、それぞれのコンテキストで定義する」のです。

そして、そのコンテキストをまたいだモデル同士の関係を明示する作業が必要になります。これがコンテキストマッピングです。

バリデーション

バリデーションはどう書けばいいのか?

バリデーションの話は、何タイプかあります。この辺はいずれ記事にしたいネタです。

  • ドメインの制約 → ドメイン層でバリデーションして例外を投げ、処理はアプリケーションに任せる
  • アプリケーションの制約 → 必要に応じて書く

ドメイン駆動設計的な設計について

この書き方でいいのか?悩みながら書いてる。ベストプラクティスはあるのか?

ありません!笑(多分)
ドメイン駆動設計はアーキテクチャの話にどうしても関わってきて、そうすると非常に多くの意思決定が必要になります。その際にドメイン駆動設計的に取りうる選択肢は一つではなく、メリット、デメリットとコストを常に考えてどこまでドメイン駆動設計の思想を取り入れるかは判断が必要になります。

なのでなかなか「これが正解」というものが作られにくいのかな、と思っていたりします。。

「ベストプラクティス」ではなく「サンプルコード」という意味であれば、実践DDDの本には、本に対応したサンプルコードがあります。まずはこの本+サンプルから入るのは良いかもしれません。

ドメイン駆動設計をまともに全部やると過設計におちいりそう。どれくらいでとどめておくべきか?

上述の通り、ドメイン駆動設計の設計は細かい意思決定の積み重ねです。

理想論をいうと、つどつどメリット・デメリット・コストをきちんと理解してバランスをとって意思決定をしていくのがよいです。ただ、最初からわからないことも多いので、ある程度はえいやで進めていく必要があります。また、意図的にコスパを考えて設計を妥協することもあります。

重要なのは、きちんと意思決定の際に「なんとなく」ではなく後から理由を答えられるようにしておくことだと思います。

CQRSについて

CQRSのクエリーで取得したデータはドメインモデルではなくなる?

取得したデータはどういうオブジェクトとして扱うべきか?
はい、クエリモデルで取得したものはドメインモデルではないと考えてOKです。クエリ用のモデルに名前をつけて概念を分けましょう。

私はクエリの戻り値にXxxDtoという名前をつけ、そのほかの場所ではDtoという名前は使わないようにしています。

なお、たまに誤解されているのを見かけるのですが。

*** CQRSで、更新系の処理でドメインモデルでDB問い合わせをしていけない訳ではありません。***

  • 更新系の処理→ドメインモデルでDBに問い合わせ、同じモデルでDB更新
  • 参照系の処理→クエリモデルでDB問い合わせ

という違いです。ご注意ください。

ドメインオブジェクトをORマッパーとやりとりする方法
現在、jooqを使って永続化とDBからのロードを実装している。実装を省く方法などないか?

ドメインモデルのORMについてですが、SpringDataJPAはかなりドメイン駆動設計を意識した思想になっており、Repositoryの実装クラスをInterfaceから自動生成してくれるので、その手間はある程度省けます。ただhibernateの仕様と戦うハメになるので、そこはお好みになります。

クエリは実際効率の良いクエリを自分でかけるのがメリットだと思うので、クエリ部分省略できるものではないと思います。クエリとそれを受けるオブジェクトのマッピングは、MyBatisを使えばある程度補助が聞くと思います。

ドメイン駆動設計の目的

ドメイン駆動設計のうれしさがふわっとしかわかってない。
何を守るのが優先なのか?


どういうメリットを得ることが優先なのか?
ここについては別の記事で詳しく書こうと思いますが、概要だけ書きます。

まず、ドメイン駆動設計には向き不向きがあり、「複雑なビジネスロジックを含むアプリケーション」向きです。シンプルなCRUDですむケースにはオーバーヘッドが大きいです。

複雑なビジネスロジックを持つアプリケーションは、開発が進むほどソースコードが複雑になり開発効率が落ちてしまいがちです。それに対して、複雑な仕様とコードをうまく制御するために対抗策が、ドメイン駆動設計だと思っています。

まずは、この目的に合致するかを判断しましょう。

何に気を使って開発するか?

モデルが正しいと思えるか

ドメイン駆動設計のメッセージは、とにかくモデルを中心に考えよう、ということです。

なので、「モデリングを適当にしてとりあえず動くものを出す」、とはせず、とにかくモデルが正しいと思えているか、という点は重要視します。

実装内容がどのレイヤーの責務か

設計する際に議論になるのがこの点です。

ドメイン層、アプリケーション層の責務定義に沿って「この実装ってどこに書くべきだっけ?」というのがある程度おのずと導かれます。開発時には常にこのことを考えるべきですし、これにより一つ骨太な設計方針が作られるので、実装のメンテナンス向上に役立ちます。

レイヤの責務

ここの理解があいまいなので、よい切り分け方を教えてほしい。

ここも別記事で詳しく書きたいですね。

  • ドメイン層:システム化対象の業務の整合を担保する
    • ドメインモデル:業務をモデリングした結果を表す   * Entity   * ValueObject   * DomainEvent
    • ドメインサービス  * Repository:モデルの永続化・永続化先からのオブジェクトの復元   * Factory:オブジェクトの生成(エンティティに任せられない複雑なもの)   * DomainService:1モデルだと表現することが出来ない責務を担う
  • アプリケーション層
    • アプリケーションサービス:ドメイン層が公開している操作を組み合わせて仕様を満たす

開発の進め方に空いて

ドメイン駆動設計 をプロジェクトに導入する際にお客様にどのような合意をいただいているのか?

自社サービスなんです。。ですが一応前職はSIerにいたので、その経験を踏まえてお答えします。

まず、アーキテクチャの話だけであるならば、採用アーキテクチャの選定にお客様の許可を取る必要があるかどうか?という点だけだと思います。そこが問題なければ任意でドメイン駆動設計のアーキテクチャを採用すればよいです。

問題は開発プロセスについてですね。ドメイン駆動設計でドメインエキスパート(≒お客様)との対話で認識を常に合わせよう、ということが言われているので、頻繁なコミュニケーションが必要になります。この機会を儲けられるかはお客様との合意が必要になります。

ただし、これは完全な個人的見解ですが、「ドメイン駆動設計やる」ということを明示する必要はなく、「これくらい認識を合わせたいんです、これくらいの頻度が必要です」という話で合意を取るのが良いかと思っています。エンジニアですらドメイン駆動設計というものを理解するのが難しいので、ビジネスサイドの方にそこまで理解してもらうのは正直難しいかな、というのが今のところは思っています。

お客様とのモデルの共有でどのような工夫をされているのか?

常にメンテしているのはER図、ただしエンジニア3人だけという規模なのはあります。

ビジネスサイドとは正直図までは共有していないです。企画の人とは新規開発時にはしていましたが、継続時にそこまで合わせてはいないです。

どちらかというと、「使う言葉を合わせよう」(≒ユビキタス言語)という程度に止まっています。正直なところ、ビジネスサイドの人とモデル図の共有はこちらもイメージがわかず、結局画面での認識合わせが一番いいよね、となっているので、これに関しては何が良いのか探り中です。

イベントソーシング

導入してるのか?いいのか?

まだしていません。

イベントソーシングについては、完全にデータ的に向き不向きがあるので、それを踏まえて判断すべきです。

例えば、購入履歴など、

  • トラブルが起きた時のダメージが大きい領域
  • トランザクション型でデータを残しているもの

に適しています。タスクの完了/未完了のような簡単な処理をイベントソーシングにする必要はなく、そこはメリット・デメリットの見極めが必要です。

サブドメインについて

keycloakを使おうと考えてます。keycloakは別の境界付けられたコンテキストとして扱うべきでしょうか?

この辺りは実装イメージと概念モデルの両面から考えるのがよいと思います。認証サーバーとして独立させるなら間違いなく別コンテキストです。

その場合はそこから受け取る情報をどうモデリングするか。永続化が必要か、など考慮して検討しましょう。

アーキテクチャについて

アーキテクチャ 三層 + ドメインモデル のやりかたがいいのか?

個人的にはオニオンアーキテクチャがオススメです。詳細は以下の記事で書いたのでご覧ください。

ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か

ドメイン駆動 + オニオンアーキテクチャ概略

ただ、本質的には

ドメインモデルがどこにも依存しない形で書ければあとは誤差

です。あとは構造や用語がしっくりくるものを選んでもらえればと思います。

UI層から受け取ったパラメータからドメインオブジェクトを作る方法

まず、オブジェクトの生成は以下の2パターンだけにしましょう。

  • 新規生成時:普通にオブジェクトのコンストラクタで作成することをおすすめ
  • 永続化済みオブジェクトのDBからの再生成時:repository内で実装。ここはなんらかのフレームワークの力を借りるのがよい

重要なのは、永続化済のオブジェクトを、画面からの入力値で生成してはいけないということです。よくあるのが、update系のメソッドで画面のformから受け取った値で、デフォルトコンストラクタから値を詰め詰めしてしまうことです。

ドメイン層は必ず整合性を担保できるメソッドのみ、アプリケーション層に公開しましょう。