Closed39

DDDの用語を整理したい

hajimismhajimism

前提:DDDについて何も知らない
オブジェクトとモデルとエンティティとバリューオブジェクトが一気に目の前に現れたので混乱している

hajimismhajimism
hajimismhajimism

このうち、 モデルを「オブジェクト(値と振る舞いを持つモノ)」として表現する のがEntityとValue Objectの2つになります。

おい、いきなり全部出てきたやんけ、いいかげんにしてください

hajimismhajimism

社員というEntityについて考えます。

山田さんという社員は、ある会社においては社員番号という識別子123(例)で同一判定され、ます。山田さんは部署が変わろうが、所持金が変わろうが、体重が変わろうが同じ「山田さん」であり、別人にはなりませんよね。
そして、それらの属性が変わるということは、本質的に可変なものである ということです。

わかりやすい

hajimismhajimism

例えば、山田さんが1つ目の10円玉を持っている状況と2つ目のの10円玉を持っている状況では、等しく山田さんのの所持金は10円と考えたいのではないでしょうか。この場合、二つをの10円を区別する必要はありません。このような場合、10円はValueObjectとしてモデリングする方が適切です。

わかるけどじゃあなんでその10円をモデリングしようとするの?

hajimismhajimism

実際には、10円玉(というValueObject)を、100円玉(というValueObject)と交換するでしょう。10円玉は製造された時に保持する金銭的価値は確定しており、あとからいかなることがあっても変わることはない。これがValueObjectが不変であるということです。

わかるようなわからないような
オブジェクトのイミュータブルな変更の話に似ている気がする

hajimismhajimism

先ほどの社員とお金のように、Entityが自身の属性としてValueObjectを保持するという関係になるのが基本となります。

オブジェクトとプロパティみたいなことでよい?

hajimismhajimism

読み終わったけどよくわからん
多分実装を見ないと意図がわからないのだと思う

hajimismhajimism
hajimismhajimism

オブジェクト毎に hogehoge_id のような一意性を表現するプロパティを含まず、一意性がない特徴です
逆にIDを持つようなオブジェクトは「Entity」といいます

hajimismhajimism

なるほど、Primitiveな値と比べてValueObjectとよんでいるのか
まじで実装都合の話だな、

class ProductPrice
  attr_reader :price

  def initialize(price)
    @price = price
  end

  def add_price(add)
    # @price = price + add # ダメ!ぜったい!
    @price + add
  end

  # 税を含めた価格を取得する
  def tax_included_price(tax=0.08)
    @price * (1 + tax)
  end

  # 価格3桁毎にカンマを入れて表示用にする
  def display_text
    @price.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse
  end
end
hajimismhajimism

イメージを掴むために寄り道
https://note.com/pol_tech/n/n261ac5986fed

hajimismhajimism

こいつは多分ValueObject

export class Email {
  private readonly _value: string;

  constructor(value: string) {
    this._value = value;
  }

  get value(): Result<string, Error> {
    return this.valid();
  }

  private valid(): Result<string, Error> {
    if (this._value.length < 1)
      return Result.Err(new Error("EmailAdressIsRequired"));

    // 正規表現チェックここにEmailの正規表現入ります。
    const pattern =  /^/;
     
    if (!pattern.test(this._value))
      return Result.Err(new Error("EmailAddressNotValid"));
    return Result.Ok(this._value);
  }
}
hajimismhajimism

Emailに関心のある操作をValueObject内に詰め込むことができる
という利点は理解した

hajimismhajimism
hajimismhajimism
  • 思想・哲学としてのDDD ...複雑になりがちなシステム開発にどう取り組むか。開発の考え方や進め方
  • 設計戦略としてのDDD (戦略的DDD) ...ドメインモデリングに繋げるためのアプローチの方針 (ドメインエキスパートとの協調、ユビキタス言語、境界づけられたコンテキスト等)
  • 実装パターンとしてのDDD (戦術的DDD) ...ドメインモデルを実装レベルで体現するためのパターン(エンティティ、リポジトリ、レイヤードアーキテクチャ等)

ぼくはいま「実装パターン」の話を調べているんだねと再確認

hajimismhajimism

「これをせずにどうやってソフトウェアを設計するのですか?」というところをやっぱり押さえたほうが良さそう。でも対抗馬がわからん。

hajimismhajimism

OOUIに触れられている:eyes:

OOUI(オブジェクト指向ユーザーインターフェース)はこのような考え方に基づいたUI設計の思想であり、OOUIを調べてみるとモデリングがどのようなものなのかイメージしやすくなるかもしれません。

DDDでは、仕様を作ることはモデルを作ることであり、そのモデルはコードで表現されるようになります。そうすると、"仕様=モデル=コード"となり、コードが仕様そのものを表すようになるはずです。

hajimismhajimism

DDDでシステムを構築する場合、ドメインの情報をクリーンに保つために、ドメイン層(ドメインモデルを記述する場所)をそれ以外の関心事と切り離して独立させることが有効です。

DDDでよく採用されているのはレイヤードアーキテクチャだと思います。その他にもヘキサゴナルアーキテクチャ、オニオンアーキテクチャなど、いろいろな切り分け方がありますが、最も重要なのはドメイン層が他の何にも依存しないようになっていることです。

Controller, Service, Repositoryで分かれてるコードベースだけみたことある。これはなにアーキテクチャ?

hajimismhajimism

(DDDにおけるEntityは)データベースやORMのエンティティとは意味が違う。単なるデータの入れ物ではない。

hajimismhajimism

エンティティはバリューオブジェクトや子エンティティを持っている場合があり、このエンティティを起点とするオブジェクトのまとまりを「集約 (Aggregate)」と呼び、起点となるエンティティを「集約ルート (Aggregate Root)」と呼ぶ

エンティティにバリューオブジェクトや子エンティティが生えているのか。そのまとまりを「集約」とするけど、バリューオブジェクトや子エンティティなどはそれ単体で意味のあるまとまりとしておきたいのか。

hajimismhajimism

コードの比較例がすごくわかりやすく、「DDD的な書き方」をしたい理由が何となく伝わる。
ふつうの書き方でphoneCall.xxx = yyyとしている処理を全部エンティティに詰め込むことで、ユースケース層のロジックがすっきりする。
ドメインを厚くしているということ(多分)

hajimismhajimism

ついでに言えば、PhoneCallの状態変化はPhoneCall内で追っかけようということだと理解した。
ユースケース層にかかれているmakePhoneCallcancelPhoneCallはあくまでUserが主体だけれど、その一部である(エンティティ内部に書かれた)startTalkingcancelはあくまでPhoneCallの状態変化で凝集している。

FooliShellFooliShell

「これをせずにどうやってソフトウェアを設計するのですか?」というところをやっぱり押さえたほうが良さそう。でも対抗馬がわからん。

あり得るアンチパターンとしては、(造語だけど)
「要望駆動設計」... 顧客が新しくシステムでやりたいことにだけ注目してしまって、顧客が既存で持っている知識が見えていない状態。
「UI駆動設計」... どういった画面が必要かということにだけ注目して、論理的な構造に対して目が向いていない状態。

とかかな

FooliShellFooliShell

Controller, Service, Repositoryで分かれてるコードベースだけみたことある。これはなにアーキテクチャ?

「どういうレイヤーがあるか」と併せて「依存関係」がどうなっているかで、アーキテクチャの種類は捉えられます。👆 依存関係の制約がない場合は「レイヤードアーキテクチャ」かな

hajimismhajimism

@FooliShell

ありがとうございます!

あり得るアンチパターンとしては、(造語だけど)

めちゃめちゃクライアントワークみを感じました。苦労がわかった気がします、なるほどですね。
「UI駆動設計」に関しては、そもそもUIが論理的な構造に着目して設計されていれば問題ない気がしています。この話はOOUIにつながるんでしょうね。

「どういうレイヤーがあるか」と併せて「依存関係」がどうなっているかで、アーキテクチャの種類は捉えられます。
「アーキテクチャ=レイヤー×依存関係」これめっちゃわかりやすいですありがとうございます!

totto2727totto2727

「UI駆動設計」... どういった画面が必要かということにだけ注目して、論理的な構造に対して目が向いていない状態。

「UI駆動設計」に関しては、そもそもUIが論理的な構造に着目して設計されていれば問題ない気がしています。この話はOOUIにつながるんでしょうね。

UI以外のトリガーでエンティティの状態が変更される場合、OOUIでは十分なモデリングができない可能性があると思いました(サーバによるバッチ処理や、UIに表示されない内部的な状態)。
DDDでは、UIから見えないものも含めてモデリングして、コードに表現できるはず…

逆に内部的に複雑なロジックが存在せず、UIを起点としてデータを変更するのみであれば、OOUIでおおよそ表現できるようになるのかな。

FooliShellFooliShell

UI以外のトリガーでエンティティの状態が変更される場合、OOUIでは十分なモデリングができない可能性があると思いました

とっとさんの感覚に自分も近いです 🙆

hajimismhajimism

たしかにそれは納得!だからサーバー側としてはドメインモデル図を握っておきたいのですね、理解
おふたりともありがとうございます!

hajimismhajimism
hajimismhajimism

Reactでこういう問題のある設計になることほとんど無いので鈍感だ
一回サーバーサイドがっつりやってみたい

「どこからでもageを変えられるのはマズそう」というのは多くの方が感覚的に共有できると思うのですが、意外と見落とされがちなのが「どこからでもageを取得できる」ことが引き起こす問題です。

hajimismhajimism

このようにドメインロジックがアプリケーションの至るところに書かれてしまっているのが「ドメイン知識が漏れている」と言われる状態で、それを引き起こしやすいのがpublicなgetterだ、という内容をここまで解説してきました。

わかりやすすぎる

hajimismhajimism

こうすることでドメインロジックがドメイン(Person)に閉じ込められて、ドメイン知識が漏れ出していない状態が実現できました。やったね!

ドメインロジックをドメインに閉じ込めるの意味を理解した

hajimismhajimism

ドメインロジックが漏れるのはフロントにも普通にあるな

hajimismhajimism

また改善後の実装もまだまだ甘く、たとえば「画像を保存する前にpersonのcanSaveを呼び出して確認しなければいけない」と言うルールが依然としてユースケースに残っています。これを解消するためにはpersonそのものを受け取り、その使用方法について把握しているドメインサービスのメソッドがcanSaveを呼び出すように変える必要がある

わかる気がするけどたぶんまだちゃんとはわかってない

hajimismhajimism

結局用語整理はよくできてないけど、DDDにおけるEntityとValueObjectが実装よりの概念であることがわかったのと、お二人のコメントによって理解が深まったので一旦満足したのでclose

このスクラップは2023/04/30にクローズされました