以前、DDD(ドメイン駆動開発)を経験した流れでいくつかのことを学びました
その中でDDDの神髄を垣間見たのでかいつまんで紹介できればと思います
記事のターゲット
DDDを学び始めた人
値オブジェクト・ValueObjectとはなにか、その片鱗を知りたい人
Value Objectとは
値オブジェクトとしてエリック本(青本)では紹介していますね
Value Objectの特徴
特徴として以下のような内容があります
- 一意性を持たない
- 計測/定量化/説明を責務とする
- イミュータブルオブジェクト
- 交換可能
- ふるまいに副作用がない
一意性を持たない
オブジェクト毎に hogehoge_id
のような一意性を表現するプロパティを含まず、一意性がない特徴です
逆にIDを持つようなオブジェクトは「Entity」といいます
この特徴の意味するところはオブジェクトをプリミティブライクに扱えることだと考えられます
これについては後述します
計測/定量化/説明を責務とする
小難しく書いていますが、例として「商品価格」「名称」といった
本来コードでは int
や string
といったプリミティブ型で表現していた内容を取り扱うということです
ここで「オブジェクトをプリミティブライクに扱える」意味が出てきます
具体的には以下のように扱うイメージです
class ProductPrice
def initialize(price)
@price = price
end
end
class FullName
def initialize(fullname)
@fullname = fullname
end
end
オブジェクト化することになんの意味があるか?と思われるかもしれないですが、後に大きな意味を持つようになります
イミュータブルオブジェクト
不変性とも言います
オブジェクトが生成された時点でオブジェクトの値が変わることを禁止しています
p = new ProductPrice(100)
# p.price = 2000 # これはダメ!!
p = new ProductPrice(2000) # 値を変える場合はオブジェクトの再生成で変更します
イミュータブルオブジェクトの利点は「オブジェクトの安全性」「スレッドセーフ」などいろいろありますが
DDDではEntityがValueObjectを多く抱えたりするので
その際にオブジェクトの状態を予想しやすくするのが重要だと捉えています
交換可能
イミュータブルの説明コードにあったように、値を変更する際にオブジェクトを交換することで実現しますが
その操作が可能である特徴です
一意性がないが故に交換可能であると言いかえることもできます
ふるまいに副作用がない
副作用とは「操作により状態が変化すること」を指しています
ValueObjectはイミュータブルである必要があるのでふるまいにより値を変更してはいけません
class ProductPrice
attr_reader :price
def initialize(price)
@price = price
end
def add_price(add)
# @price = price + add # ダメ!ぜったい!
@price + add
end
end
これも安全性の観点で非常に大事な特徴です
クラス利用者はメソッドを呼ぶときに変な気苦労をする必要がありません
つらつらと特徴について説明しましたが、あくまでValueObjectの規約みたいなもので
その価値についてはまだ触れていませんでした
Value Objectの利点
あえてプリミティブ型を使わずにオブジェクトでやる理由がほしいですね
誤解を恐れずにいうと値の持つビジネスルール(仕様)を豊かに表現できることが最大のメリットです
先ほどの例でメリットをみてみましょう
class ProductPrice
attr_reader :price
def initialize(price)
@price = price
end
def add_price(add)
# @price = price + add # ダメ!ぜったい!
@price + add
end
end
これだけではただの使いにくい値ですが、表示や複雑な計算をするようになると一変します
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
表現力が増してただの値ではどうしようもないところもカバーできるようになりました
上記は単純なプロパティのみですが、実務では更に複雑なふるまいも増えてきますのでより効果的ですね
更にTaxとかもValueObjectにすることで税部分が複雑になっても切り分けて扱ったりできるので広がりも大きいです
なかなかイメージしにくいですが、ValueObjectは実践できればコードの見通しが良くなるので
知らなかった方は意識してコードを書いてみてはいかがでしょう?
(かくいう私もまだまだ実践できていないので精進します…)
その他
軽量DDDでもやったほうがいい理由、けどやらない理由
レイヤードアーキテクチャの視点
Serviceクラスの意義と勘所
集約で境界を正しく表現する意味