Site cover image
DDDを学ぶ ~ 値オブジェクト ~

書籍等で学んだ知識について、ブログでのアウトプットを始めました。記念すべき初ポストです。

暖かい目で見ていただけると幸いです。

値オブジェクト については いくつか定義があるよう なのですが、ここでは Eric Evans の DDD 文脈で調べたことをまとめています。

値オブジェクトについては多義あるようですし、実際に使っているわけではなく勉強しただけなので間違っていたり考慮の足りない部分があるかもしれません。その場合はご容赦ください。

主に、以下のような特徴を持たせたオブジェクトのことを「値オブジェクト」と呼んでいるようです。

  1. 不変である
  2. 値によって等価性が判断される

値オブジェクトそれ自体は、いったん作ってしまったら変更ができません。

もし、あるエンティティの属性として値オブジェクトを使用している場合は、属性を変更するには値オブジェクトを置き換えるしかないということを意味します。

例えば、服を組み合わせて作るファッションコーディネートを例として考えてみます。

shoes を変更したい場合は、別の値オブジェクトで置き換えるしかなく、値オブジェクト自体を変更することはできません。

tops = Shoes.new(name: 'hoge')
cordinate = FashionCordinate(shoes: shoes)

// OK
cordinate.shoes = Shoes.new(name: 'fuga')
// NG
cordinate.shoes.name = 'fuga'

エンティティが識別子によって等価性が判断されるのと異なり、値オブジェクトは値(属性)によって等価性が判断されます。

服の例で考えてみます。

例えばファッションコーディネートの場合は、商品名と金額にしか興味がないので、それらが一致すれば同じとして良いでしょう。この場合、服は値オブジェクトとして取り扱うのが良いでしょう。

cloth_a = Cloth.new(name: '2024 Hogehoge Pants', price: 1000)
cloth_b = Cloth.new(name: '2024 Hogehoge Pants', price: 1000)
cloth_c = Cloth.new(name: '2024 Hogehoge Pants', price: 900) # 値下げした!

cloth_a.equal?(cloth_b)
# -> true
cloth_a.equal?(cloth_c)
# -> false

一方、在庫管理サービスでは同じ商品でもモノによって区別をしたいため、一意のIDを付与して管理する必要があります。その場合は、商品名が同じだとしても別のモノとなり、例え値下げで金額が変わったとしても同じ服として判断されます。

服はエンティティとなるでしょう。

cloth_a = Cloth.new(id: 1, name: '2024 Hogehoge Pants', price: 1000).save
cloth_b = Cloth.new(id: 2, name: '2024 Hogehoge Pants', price: 1000).save
cloth_c = Cloth.find(1)
cloth_c.price = 900 # 値下げした!
cloth_c.save

cloth_a.equal?(cloth_b)
# -> false
cloth_a.equal?(cloth_c)
# -> true

エリック・エヴァンスのドメイン駆動設計 においては以下と書かれています。

エンティティの同一性を追跡するのは本質的なことだが、それ以外のオブジェクトに同一性を与えてしまうと、システムの性能を損なうことになり、分析作業がふえ、さらに、すべてのオブジェクトの見た目が同じになってしまうことでモデルが台無しになりかねない。

ソフトウェア設計は、複雑さとの恒常的な戦いである。特別な処理が必要な場所だけで行われるように、区別しなければならない。

しかし、このオブジェクトのカテゴリを、単に同一性のないものと見なしてしまうと、我々の使えるツールや語彙は大して増えない。実のところ、これらのオブジェクトには、独自の特徴とモデルに対する独自の意味がある。これは物事を記述するオブジェクトなのだ。

上記から、その個別性についてはフォーカスされないドメインを表現する手段として、DDDでは値オブジェクトを導入しているのだと思われます。

値オブジェクトのメリットとしては主に以下があげられるようです。

  • 安全性の向上
  • 可読性の向上
  • 保守性の向上

値オブジェクトにバリデーションを実装することにより、安全性を向上させることができます。

服の例で言えば、バリデーションを実装することによって Shoes の値オブジェクトに Pants や Socks が入ったり、price フィールドに負の値が入ったりすることを防ぐことができます。

プリミティブな値を使っている場合は、不正な値が入ってしまう可能性が否定できません。

また、不変性がなく状態が変わっている可能性があるとするならば、バグの温床となることは間違いありません。

値オブジェクトを利用することによって、どういう値なのかやどういう振る舞いを持つのかの共通認識を作ることができます。

Shoes という値オブジェクトであれば、 Pants や Socks が入らないことがすぐにわかります。

アプリケーション内で共通して使うことで、可読性を高めることができます。

プリミティブな値を使っている場合は、どういう値を想定しているのか、簡単に読み解くことはできません。

参考:

値オブジェクトにバリデーションや振る舞いを持たせ、アプリケーション内で共通して使うことで、ロジックを1箇所にまとめることができます。

プリミティブな値を散在して使ってしまうと、振る舞いを変更したい場合に改修コストが嵩みます。

メリットが多いように思いますが、エンティティの属性全てを値オブジェクトにすれば良いのかというと、それはやりすぎなように思えます。

やりすぎてしまえば、モデルの数やコード量が増えて煩わしさの方が優ってしまうでしょう。

実践まではできていないので想像になってしまいますが、不変性、値による透過性に加えて、少なくとも以下の要素を満たしていることが大事なのではないかと思えます。

  • ドメイン固有の制約、もしくは振る舞いを持っている
  • アプリケーション内の複数の箇所で使われている

やはりDDDの文脈で採用されているだけあって、ドメインとして表現したいかが一番重要なのかなと想像しています。

値オブジェクトについては多義あるようですし実践までしているわけではないので、頓珍漢なことや現実に即していないことも言っているかもしれません。その場合はご容赦ください。

実際、服の例を出していますが、実際のDBを利用したアプリケーションでは、一意の識別子は持たせるでしょうし、ドメイン知識を入れていくと結局エンティティとしたくなるような気はします。

ただ、DBに保存していたとしても不変性や値による等価性は使えそうな気がしますし、エッセンスとしては抑えてよくとより設計が深まりそうな感じがしました。

安全性については、セキュア・バイ・デザインでも取り上げられているようで、そこまで読めていないので近々読んでみようと思います。

それでは。

エリック・エヴァンスのドメイン駆動設計: ソフトウェアの核心にある複雑さに立ち向かう エリック エヴァンス (著)

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 成瀬 允宣 (著)

実践ドメイン駆動設計 ヴォーン・ヴァーノン (著)

Thank you!
Thank you!
URLをコピーしました

コメントを送る

コメントはブログオーナーのみ閲覧できます