IDを渡すべきかドメインオブジェクトを渡すべきか
#プログラミング
windymelt.icon 誰か書き込んで(割り込み歓迎)
windymelt.icon
ドメインオブジェクトを渡したいとき、2つやりかたがある
IDを渡して、向こうでよしなにやってもらう
someAction(foo.id)
そのオブジェクトをそのまま渡す
someAction(foo)
Actionと書いてるけどドメインアクションとはそんなに関係なくて、ドメインオブジェクトもらって何かするくらいの意味しかない
どっちがエエのか?
windymelt.icon はオブジェクトをそのまま渡すべきと考えている
主張の前提
強い型システムを使える
感じているメリット
直感的である
ID同士だと取り違えるリスクがある
強い型システムを持つ言語ではそれも回避できる
そもそも型システムがよわいとオブジェクト渡しても大して嬉しくない
これで困る例
多対多の関係を扱う場合
外部システムとのやりとりである場合
典型的にはDB
典型的にはユーザがURLを入力するとき
少ないはず
そもそもオブジェクトのIDという概念は通常は表に出てこない特殊な概念なはず
自然界にIDはない
しかし管理上の都合でidentifyする必要があるのでそうしているにすぎない
RGBで表現される色にIDはない
値がそのままアイデンティティだから
人間にはIDがある
なんらかの意味で同一性を追跡し続ける必要があるから
卑近な例では、名字がかわっても同一人物とみなす必要がある
概念的には値渡しと参照渡しになるbsahd.icon
windymelt.iconイミュータブルな言語だとあまり区別しなくてよくなる?
Twitterにも同じことを書いてたのの転記です
XユーザーのWindymelt💀(めるくん)🚀❤️‍🔥さん: 「素朴な疑問なのだけれど、 someActionForSomeDomainObjectという処理があるとき、 (SomeDomainObjectId) => ResultType というシグネチャにするのと、 (SomeDomainObject) => ResultType というシグネチャにするのが考えられるけれど、 みんなどっちで書いてますか。基準とかある?」 / X
基本的に関数がIDを引数に取るのは集約を解決する場合が殆どRayStarkMC.icon
DBからの読み込みはこのケースに該当するからIDを引数に取る
たまに集約がエンティティのコレクションを持っている場合にIDで存在チェックしたりする
かつ、そうでないロジックが集約の全てのプロパティを消費するロジックも稀
モデルの永続化はこれに該当するから素直にモデルを引数に取る
純粋なロジック毎に入力型と出力型を用意しておいて、集約から入力型を抽出する関数を定義する
このロジックの引数をIDにすると、リポジトリへの問い合わせが発生して不純になる
このロジックの引数を集約にすると、ロジックが集約のデータの持ち方に影響を受ける
windymelt.icon*3
データ型用意するのが手間なら普通の関数として定義しても良い
だいたい以下のパターン
時々pureLogicによる判断の後に追加でDBにアクセスしないといけないケースがある
Domain Modeling Made Functionalのsandwitchパターンに持っていく
DBアクセス -> 純粋ロジック -> DBアクセス -> 純粋ロジック -> DBアクセス -> ...
code:scala
class SomeCommand {
// 依存関係を取るなりコマンド内でベタ書きするなり
val readModel: ID => Model = ???
val writeModel: Model => () = ???
val pureLogic: LogicInput => LogicOutput = ???
val deriveLogicInput: Model => LogicInput = ???
val deriveOutput LogicOutput => Output = ???
//FutureとかEitherとか略
def execute(input: Input): Output {
val model = readModel(input.id) //副作用
/*ここから純粋*/
val logicInput = deriveInput(model)
val logicOutput = pureLogic(logicInput)
val output = deriveOutput(logicOutput)
val modifiedModel = ??? //outputを使ってmodel更新
/*ここまで純粋*/
writeModel(modifiedModel) //副作用
output
}
}
object SomeCommand {
case class Input(/*略*/)
case class Output(/*略*/)
}
Twitterで得た意見
IDを授受しているとテストしやすい
windymelt.icon どうだろう?
ドメインロジックを試したいのであれば、ドメインオブジェクトをそのまま渡すのがスジなはず
他方で、テストデータ(fixture)作成の都合を考えるとIDで管理したい
テストの都合でドメインを歪めていいのか?
IDだと重くなったり壊れたりしない
windymelt.icon 疑問
引数に何かを渡すとき、メモリがそのままコピーされてどこかへ行くわけではない
値そのものか、それを指すポインタ(に類するもの)が入っている
まともな言語ならそうなっている
IDと何かへの参照との間にデータ上の大きさの差異はない
もし参照を渡して、内部でIDを結局利用するとき、参照が一回挟まるという意味ではオーバーヘッドがある
100万回ループを回すのでなければほぼ誤差の範囲では?
DBやネットワークレベルのI/Oのほうがオーダー数ケタレベルで遅い
2つid渡して2回SQL発行とか絶対嫌
windymelt.icon 結局向こうで引いてたら意味ないじゃん、という意味でならそう思う
オニオンアーキテクチャで言うところのドメイン層に処理があるなら後者(オブジェクトを授受する)、アプリケーション層にあるなら加えて前者(ID)もアリにしてます。ドメイン層にIdから実体をフェッチする概念は置きたくないので。
windymelt.icon わかる。ドメイン層にはIDの概念はほぼ出現しないはず
集約を引いてくるときはIDを利用するがそれ以外のパターンでは利用しない
windymelt.icon わかる。それは本質的にIDがないと困る
Twitterで得た意見を総合した感想
windymelt.icon
ドメインの内側にはIDの概念はない
ドメインの外側でリポジトリからドメインオブジェクトを取り出してくるときにはIDが必要になる
それはそう
集約を引いてくるときはIDが必要になる
が、それはドメインの仕事ではなくリポジトリやサービスの仕事